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::memo_attribution::{ReplayingMemo, ReplayingMemoEntry};
38use brokk_bifrost_core::analyzer::model::{
39    CallableArity, CallableLinkage, CodeUnitType, CppFieldLinkage, CppTemplateExpression,
40    CppTemplateMetadata, CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect,
41    SignatureMetadata, StructuredTypeName,
42};
43use brokk_bifrost_core::analyzer::pool_memo::{KeyedPoolSafeMemo, PoolSafeMemo};
44use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
45#[cfg(test)]
46use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
47use brokk_bifrost_core::analyzer::project_reads::{OpaqueReadReceipt, ReadCapability};
48use brokk_bifrost_core::analyzer::query_token::QueryToken;
49use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_range_contains;
50use brokk_bifrost_core::analyzer::source_facts::SourceDeclarationId;
51use brokk_bifrost_core::analyzer::structural::adapter_helpers::{field_name_in_parent, node_range};
52use brokk_bifrost_core::analyzer::tree_walk::{
53    ParentIndex, child_by_field_or_named_child_of_kinds, children_iter, first_named_child_of_kind,
54    named_children_iter, node_for_exact_range, push_named_children_reversed,
55    single_named_child_matching,
56};
57use brokk_bifrost_core::analyzer::usages::common::same_node;
58use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
59use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
60use brokk_bifrost_core::cancellation::CancellationToken;
61use brokk_bifrost_core::hash::{HashMap, HashSet};
62#[cfg(test)]
63use brokk_bifrost_core::text_utils::compute_line_starts;
64use std::borrow::Cow;
65#[cfg(any(test, feature = "test-support"))]
66use std::cell::Cell;
67use std::cell::OnceCell;
68use std::cmp::Ordering as CmpOrdering;
69use std::collections::BTreeSet;
70use std::hash::Hash;
71use std::sync::atomic::{AtomicUsize, Ordering};
72use std::sync::{Arc, Mutex, OnceLock};
73use std::time::{Duration, Instant};
74use tree_sitter::{Node, Parser, Tree};
75
76const PREPROCESSOR_CONTEXT_CACHE_CAP: usize = 8192;
77const CALLABLE_METADATA_CACHE_CAP: usize = 512;
78const MACRO_LEXICAL_INDEX_CACHE_CAP: usize = 64;
79
80#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
81struct PreprocessorContextNodeKey {
82    id: usize,
83    start_byte: usize,
84    end_byte: usize,
85}
86
87/// Query-local memo for malformed-preprocessor boundary discovery.
88///
89/// The boundary is a property of one conditional AST node, but the resolver
90/// asks for it once per declaration below that node and once in each of the
91/// guard/family passes. Keeping this memo on the C++ visitor avoids repeating
92/// the bounded subtree scans without retaining anything after one translation
93/// unit is parsed. A full cache falls back to the uncached computation, so its
94/// memory is explicitly capped and its admission cannot affect answers.
95#[derive(Default)]
96pub(crate) struct PreprocessorContextCache {
97    displaced_boundaries: HashMap<PreprocessorContextNodeKey, Option<usize>>,
98    displaced_branches: HashMap<PreprocessorContextNodeKey, Option<DisplacedBranchDirective>>,
99}
100
101/// The end byte of a [`cpp_displaced_preprocessor_branch`] directive and
102/// whether it is an `#else` (otherwise an `#elif` form).
103#[derive(Clone, Copy)]
104struct DisplacedBranchDirective {
105    end_byte: usize,
106    is_else: bool,
107}
108
109impl PreprocessorContextCache {
110    fn displaced_branch(&mut self, conditional: Node<'_>) -> Option<DisplacedBranchDirective> {
111        let key = PreprocessorContextNodeKey {
112            id: conditional.id(),
113            start_byte: conditional.start_byte(),
114            end_byte: conditional.end_byte(),
115        };
116        if let Some(directive) = self.displaced_branches.get(&key) {
117            return *directive;
118        }
119        let directive = cpp_displaced_preprocessor_branch(conditional).map(|directive| {
120            DisplacedBranchDirective {
121                end_byte: directive.end_byte(),
122                is_else: directive.kind() == "#else",
123            }
124        });
125        if self.displaced_branches.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
126            self.displaced_branches.insert(key, directive);
127        }
128        directive
129    }
130
131    fn displaced_boundary_end(&mut self, conditional: Node<'_>) -> Option<usize> {
132        let key = PreprocessorContextNodeKey {
133            id: conditional.id(),
134            start_byte: conditional.start_byte(),
135            end_byte: conditional.end_byte(),
136        };
137        if let Some(boundary) = self.displaced_boundaries.get(&key) {
138            return *boundary;
139        }
140        let boundary =
141            cpp_displaced_preprocessor_boundary(conditional).map(|boundary| boundary.end_byte);
142        if self.displaced_boundaries.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
143            self.displaced_boundaries.insert(key, boundary);
144        }
145        boundary
146    }
147}
148
149#[cfg(any(test, feature = "test-support"))]
150thread_local! {
151    static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
152    static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
153}
154
155#[derive(Clone, Copy, PartialEq, Eq)]
156pub enum TargetKind {
157    Type,
158    Constructor,
159    FreeFunction,
160    Method,
161    GlobalField,
162    MemberField,
163    Macro,
164}
165
166pub enum LexicalTypeResolution {
167    Resolved {
168        unit: CodeUnit,
169        components: Vec<String>,
170        candidates: Vec<CodeUnit>,
171    },
172    Ambiguous,
173    Missing,
174}
175
176/// What a reference's guard and build evidence selects from the conditional
177/// alias arms on its alias chain.
178pub enum GuardedAliasSelection {
179    /// Evidence implies one arm at every conditional hop and the chain names
180    /// exactly `unit`. `candidates` are the selected top-level declarations.
181    Proven {
182        unit: CodeUnit,
183        candidates: Vec<CodeUnit>,
184    },
185    /// Undecided or unresolvable arms remain. Each type is a candidate only.
186    Alternatives(Vec<CodeUnit>),
187}
188
189#[derive(Clone, Copy, PartialEq, Eq)]
190enum AliasArmEvidence {
191    Implied,
192    Compatible,
193    Contradicted,
194}
195
196#[derive(Clone, Copy)]
197enum TypeCandidateResolution<'a> {
198    Canonical,
199    PreserveAlias,
200    PreserveTarget(&'a CodeUnit),
201}
202
203/// Why a name did not reduce to one indexed type declaration.
204///
205/// The two answers are not interchangeable. `Ambiguous` means the index holds
206/// several declarations and the caller must choose; `Unresolvable` means the
207/// index holds none, which is a boundary the workspace cannot see past. A
208/// `using`/`typedef` alias to a template parameter or to a standard-library
209/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
210/// answer with an empty candidate list (#1828).
211#[derive(Clone, Copy, Debug, PartialEq, Eq)]
212enum TypeCandidateFailure {
213    Ambiguous,
214    Unresolvable,
215}
216
217impl TypeCandidateFailure {
218    fn lexical_resolution(self) -> LexicalTypeResolution {
219        match self {
220            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
221            Self::Unresolvable => LexicalTypeResolution::Missing,
222        }
223    }
224}
225
226pub enum LexicalCallableValueResolution {
227    Type(CodeUnit),
228    FreeFunction(CodeUnit),
229    Ambiguous,
230    Missing,
231}
232
233pub enum UsingEnumMemberResolution {
234    Resolved { owner: CodeUnit, member: CodeUnit },
235    Ambiguous,
236    Missing,
237}
238
239pub enum NamespaceValueResolution {
240    Resolved,
241    Ambiguous,
242    Missing,
243}
244
245#[derive(Clone, Debug, PartialEq, Eq)]
246pub enum OrdinaryMacroReferenceResolution {
247    Resolved(CodeUnit),
248    Ambiguous,
249    Missing,
250}
251
252#[derive(Clone, Debug, PartialEq, Eq)]
253pub enum RecoveredCReferenceRanges {
254    Complete(Vec<Range>),
255    LimitExceeded,
256}
257
258pub fn resolve_namespace_value(
259    analyzer: &CppGraphSource<'_>,
260    visibility: &VisibilityIndex<'_>,
261    file: &ProjectFile,
262    namespace: &str,
263    name: &str,
264    before_byte: usize,
265) -> NamespaceValueResolution {
266    let mut matches = Vec::new();
267    for candidate in visibility.visible_identifier_candidates(file, name) {
268        if type_owner_of(analyzer, candidate).is_some()
269            || candidate.package_name() != namespace
270            || (candidate.source() == file
271                && !analyzer
272                    .ranges(candidate)
273                    .iter()
274                    .any(|range| range.start_byte < before_byte))
275            || matches
276                .iter()
277                .any(|existing| same_visible_symbol(existing, candidate))
278        {
279            continue;
280        }
281        matches.push(candidate.clone());
282        if matches.len() > 1 {
283            return NamespaceValueResolution::Ambiguous;
284        }
285    }
286    matches
287        .pop()
288        .map(|_| NamespaceValueResolution::Resolved)
289        .unwrap_or(NamespaceValueResolution::Missing)
290}
291
292pub(crate) struct ScopedUsingEnumOwners {
293    scopes: Vec<Vec<CodeUnit>>,
294}
295
296/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
297/// Cross-file and inherited class imports are deliberately not inferred without persisted
298/// evidence; a missing imported enumerator therefore remains unproven rather than being
299/// misresolved.
300pub(crate) struct SemanticUsingEnumOwners {
301    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
302    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
303}
304
305pub(crate) enum SemanticUsingEnumMemberResolution {
306    Class(UsingEnumMemberResolution),
307    Namespace(UsingEnumMemberResolution),
308    Missing,
309}
310
311impl SemanticUsingEnumOwners {
312    pub(crate) fn new() -> Self {
313        Self {
314            class_imports: HashMap::default(),
315            namespace_imports: HashMap::default(),
316        }
317    }
318
319    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
320        let imports = self.class_imports.entry(class).or_default();
321        if !imports
322            .iter()
323            .any(|existing| same_visible_symbol(existing, &enum_owner))
324        {
325            imports.push(enum_owner);
326        }
327    }
328
329    pub fn import_namespace(
330        &mut self,
331        namespace: Vec<String>,
332        declaration_byte: usize,
333        enum_owner: CodeUnit,
334    ) {
335        let imports = self.namespace_imports.entry(namespace).or_default();
336        if !imports
337            .iter()
338            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
339        {
340            imports.push((declaration_byte, enum_owner));
341        }
342    }
343
344    pub fn resolve_member(
345        &self,
346        visibility: &VisibilityIndex<'_>,
347        file: &ProjectFile,
348        class: Option<&CodeUnit>,
349        namespace: &[String],
350        before_byte: usize,
351        name: &str,
352    ) -> SemanticUsingEnumMemberResolution {
353        if let Some(class) = class
354            && let Some((_, imports)) = self
355                .class_imports
356                .iter()
357                .find(|(owner, _)| same_visible_symbol(owner, class))
358        {
359            let resolution =
360                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
361            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
362                return SemanticUsingEnumMemberResolution::Class(resolution);
363            }
364        }
365        for prefix_len in (0..=namespace.len()).rev() {
366            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
367                continue;
368            };
369            let owners = imports
370                .iter()
371                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
372                .map(|(_, owner)| owner);
373            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
374            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
375                return SemanticUsingEnumMemberResolution::Namespace(resolution);
376            }
377        }
378        SemanticUsingEnumMemberResolution::Missing
379    }
380}
381
382fn resolve_using_enum_member_for_owners<'a>(
383    visibility: &VisibilityIndex<'_>,
384    file: &ProjectFile,
385    owners: impl IntoIterator<Item = &'a CodeUnit>,
386    name: &str,
387) -> UsingEnumMemberResolution {
388    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
389    for owner in owners {
390        for member in visibility.visible_members_for_owner_name(file, owner, name) {
391            if !member.is_field()
392                || matches.iter().any(|(existing_owner, existing_member)| {
393                    same_visible_symbol(existing_owner, owner)
394                        && same_visible_symbol(existing_member, member)
395                })
396            {
397                continue;
398            }
399            matches.push((owner.clone(), member.clone()));
400        }
401    }
402    match matches.len() {
403        0 => UsingEnumMemberResolution::Missing,
404        1 => {
405            let (owner, member) = matches.pop().expect("one using-enum match");
406            UsingEnumMemberResolution::Resolved { owner, member }
407        }
408        _ => UsingEnumMemberResolution::Ambiguous,
409    }
410}
411
412impl ScopedUsingEnumOwners {
413    pub(crate) fn new() -> Self {
414        Self {
415            scopes: vec![Vec::new()],
416        }
417    }
418
419    pub fn enter_scope(&mut self) {
420        self.scopes.push(Vec::new());
421    }
422
423    pub fn exit_scope(&mut self) {
424        brokk_bifrost_core::analyzer::usages::exit_nested_scope(&mut self.scopes);
425    }
426
427    pub fn import(&mut self, owner: CodeUnit) {
428        let scope = self
429            .scopes
430            .last_mut()
431            .expect("using-enum scope stack is never empty");
432        if !scope
433            .iter()
434            .any(|existing| same_visible_symbol(existing, &owner))
435        {
436            scope.push(owner);
437        }
438    }
439
440    pub fn resolve_member(
441        &self,
442        visibility: &VisibilityIndex<'_>,
443        file: &ProjectFile,
444        name: &str,
445    ) -> UsingEnumMemberResolution {
446        for scope in self.scopes.iter().rev() {
447            let resolution =
448                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
449            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
450                return resolution;
451            }
452        }
453        UsingEnumMemberResolution::Missing
454    }
455}
456
457#[derive(Clone)]
458pub struct TargetSpec {
459    pub target: CodeUnit,
460    pub kind: TargetKind,
461    pub owner: Option<CodeUnit>,
462    pub member_name: String,
463    pub callable_arity: Option<CallableArity>,
464    pub activated_callable_arities: Vec<ActivatedCallableArity>,
465    pub param_types: Option<Vec<String>>,
466    pub enum_owner_kind: EnumOwnerKind,
467    pub owner_is_forward_declaration: bool,
468    pub callable_has_definition_body: bool,
469}
470
471#[derive(Clone, Copy)]
472pub struct ActivatedCallableArity {
473    pub activation_byte: usize,
474    pub arity: CallableArity,
475}
476
477#[derive(Debug, PartialEq, Eq, Hash)]
478pub struct TypeScanKey {
479    target: LogicalSymbolKey,
480    member_name: String,
481}
482
483#[derive(Clone, Debug, PartialEq, Eq, Hash)]
484struct LogicalSymbolKey {
485    kind: CodeUnitType,
486    fq_name: String,
487    signature: Option<String>,
488}
489
490struct ResolvedTypeOwner {
491    unit: CodeUnit,
492    is_forward_declaration: bool,
493}
494
495impl TargetSpec {
496    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
497        (self.kind == TargetKind::Type).then(|| TypeScanKey {
498            target: logical_symbol_key(&self.target),
499            member_name: self.member_name.clone(),
500        })
501    }
502
503    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
504        if target.is_class() {
505            return Some(Self::new(
506                target.clone(),
507                TargetKind::Type,
508                Some(target.clone()),
509                target.identifier().to_string(),
510                None,
511                None,
512            ));
513        }
514
515        if target.is_field() {
516            // A namespace (module) is not a receiver: a namespace-scoped constant such as
517            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
518            // qualified from outside, exactly like a global. Treating a module owner as a
519            // member-field owner makes the receiver/owner-context match reject every valid
520            // reference, so resolve it as a global field instead.
521            let owner = type_owner_of(analyzer, target);
522            let kind = if owner.is_some() {
523                TargetKind::MemberField
524            } else {
525                TargetKind::GlobalField
526            };
527            let enum_owner_kind = owner
528                .as_ref()
529                .map(|owner| classify_enum_owner(analyzer, owner))
530                .unwrap_or(EnumOwnerKind::NonEnum);
531            let mut spec = Self::new(
532                target.clone(),
533                kind,
534                owner,
535                target.identifier().to_string(),
536                None,
537                None,
538            );
539            spec.enum_owner_kind = enum_owner_kind;
540            return Some(spec);
541        }
542
543        if target.is_function() {
544            // Free functions declared inside a namespace have a module owner; that namespace is
545            // not a call receiver, so resolve them as free functions rather than methods.
546            let owner_resolution = target_type_owner_resolution(analyzer, target);
547            let owner_is_forward_declaration = owner_resolution
548                .as_ref()
549                .is_some_and(|owner| owner.is_forward_declaration);
550            let owner = owner_resolution.map(|owner| owner.unit);
551            let kind = if owner.as_ref().is_some_and(|owner| {
552                target.identifier() == owner.identifier()
553                    || analyzer
554                        .cpp
555                        .and_then(|cpp| cpp.template_metadata(owner))
556                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
557            }) {
558                TargetKind::Constructor
559            } else if owner.is_some() {
560                TargetKind::Method
561            } else {
562                TargetKind::FreeFunction
563            };
564            let mut spec = Self::new(
565                target.clone(),
566                kind,
567                owner,
568                target.identifier().to_string(),
569                cpp_callable_arity(analyzer, target),
570                cpp_callable_parameter_types(analyzer, target),
571            );
572            spec.owner_is_forward_declaration = owner_is_forward_declaration;
573            spec.callable_has_definition_body =
574                callable_target_has_definition_body(analyzer, target);
575            return Some(spec);
576        }
577
578        if target.is_macro() {
579            return Some(Self::new(
580                target.clone(),
581                TargetKind::Macro,
582                None,
583                target.identifier().to_string(),
584                None,
585                None,
586            ));
587        }
588
589        None
590    }
591
592    pub fn with_visible_callable_arities<'a>(
593        &'a self,
594        analyzer: &CppGraphSource<'_>,
595        cpp: &dyn CppSource,
596        visibility: &VisibilityIndex<'_>,
597        file: &ProjectFile,
598        prepared: &PreparedSyntaxTree,
599    ) -> Cow<'a, Self> {
600        let macro_parameter_arity =
601            visibility.callable_parameter_macro_arity(&self.target, self.param_types.as_deref());
602        let activated_callable_arities =
603            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
604        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
605            return Cow::Borrowed(self);
606        }
607        let mut effective = self.clone();
608        if let Some(macro_parameter_arity) = macro_parameter_arity {
609            effective.callable_arity = Some(macro_parameter_arity);
610        }
611        effective.activated_callable_arities = activated_callable_arities;
612        Cow::Owned(effective)
613    }
614
615    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
616        let base = self.callable_arity?;
617        Some(
618            self.activated_callable_arities
619                .iter()
620                .filter(|candidate| candidate.activation_byte <= byte)
621                .fold(base, |arity, candidate| {
622                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
623                }),
624        )
625    }
626
627    pub fn new(
628        target: CodeUnit,
629        kind: TargetKind,
630        owner: Option<CodeUnit>,
631        member_name: String,
632        callable_arity: Option<CallableArity>,
633        param_types: Option<Vec<String>>,
634    ) -> Self {
635        Self {
636            target,
637            kind,
638            owner,
639            member_name,
640            callable_arity,
641            activated_callable_arities: Vec::new(),
642            param_types,
643            enum_owner_kind: EnumOwnerKind::NonEnum,
644            owner_is_forward_declaration: false,
645            callable_has_definition_body: false,
646        }
647    }
648}
649
650fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
651    analyzer
652        .signature_metadata(target)
653        .iter()
654        .any(|metadata| !metadata.is_declaration_only())
655}
656
657fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
658    LogicalSymbolKey {
659        kind: unit.kind(),
660        fq_name: unit.fq_name(),
661        signature: unit.signature().map(str::to_string),
662    }
663}
664
665fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
666    let Some(facts) = analyzer.declaration_source_properties(owner) else {
667        return EnumOwnerKind::NonEnum;
668    };
669    let Some(first) = facts.first() else {
670        return EnumOwnerKind::NonEnum;
671    };
672    if facts.iter().all(|fact| fact.enum_kind == first.enum_kind) {
673        first.enum_kind
674    } else {
675        EnumOwnerKind::NonEnum
676    }
677}
678
679#[derive(Clone, PartialEq, Eq, Hash)]
680pub struct CppScanBinding {
681    pub unit: Option<CodeUnit>,
682    pub type_name: Option<String>,
683    pub indirection: i32,
684}
685
686impl CppScanBinding {
687    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
688        Self::from_type_name(cpp_name_for(&unit), Some(unit), indirection)
689    }
690
691    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
692        Self {
693            type_name: Some(type_name),
694            unit,
695            indirection,
696        }
697    }
698
699    pub fn as_arg_type(&self) -> Option<CppArgType> {
700        let name = self
701            .type_name
702            .clone()
703            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
704        Some(CppArgType {
705            name,
706            unit: self.unit.clone(),
707            indirection: self.indirection,
708            pointee_const: false,
709        })
710    }
711}
712
713// Visibility data is shared by several captures in one definition batch.
714// Receipts belong to completed values, not to allocation of a lazy cell.
715type VisibilityEntry<T> = Arc<ReplayingMemoEntry<T, OpaqueReadReceipt>>;
716type VisibilityMemo<K, V> =
717    ReplayingMemo<KeyedPoolSafeMemo<K, ReplayingMemoEntry<V, OpaqueReadReceipt>>>;
718type BoundedVisibilityMemo<K, V> = ReplayingMemo<Mutex<HashMap<K, VisibilityEntry<V>>>>;
719// Keep the existing map accounting projection while each cell owns a receipt.
720type IncludeProtectionMemo = ReplayingMemo<
721    Mutex<
722        HashMap<
723            ProjectFile,
724            Arc<PoolSafeMemo<ReplayingMemoEntry<MacroIncludeProtection, OpaqueReadReceipt>>>,
725        >,
726    >,
727>;
728type VisibilityOnce<T> = ReplayingMemo<OnceLock<VisibilityEntry<T>>>;
729
730fn try_visibility_memo_value<K: Eq + Hash + Clone, V: Clone, E>(
731    memo: &VisibilityMemo<K, V>,
732    reads: &ReadCapability,
733    key: K,
734    build: impl FnOnce() -> Result<V, E>,
735) -> Result<V, E> {
736    let value = memo.read(&reads.memo_attribution(), |entries, capture| {
737        entries
738            .cell(&key)
739            .get_or_try_build_pool_independent(|| capture.try_build(build))
740    })?;
741    Ok((*value).clone())
742}
743
744pub(crate) fn visibility_memo_value<K: Eq + Hash + Clone, V: Clone>(
745    memo: &VisibilityMemo<K, V>,
746    reads: &ReadCapability,
747    key: K,
748    build: impl FnOnce() -> V,
749) -> V {
750    try_visibility_memo_value(memo, reads, key, || {
751        Ok::<_, std::convert::Infallible>(build())
752    })
753    .expect("infallible C++ visibility build")
754}
755
756fn bounded_visibility_memo_value<K: Eq + Hash + Clone, V: Clone>(
757    memo: &BoundedVisibilityMemo<K, V>,
758    reads: &ReadCapability,
759    key: &K,
760    capacity: usize,
761    build: impl FnOnce() -> V,
762) -> V {
763    let value = memo
764        .read(&reads.memo_attribution(), |entries, capture| {
765            if let Some(value) = entries
766                .lock()
767                .expect("C++ bounded visibility memo poisoned")
768                .get(key)
769                .cloned()
770            {
771                return Ok::<_, std::convert::Infallible>(value);
772            }
773            let value = Arc::new(capture.build(build));
774            let mut entries = entries
775                .lock()
776                .expect("C++ bounded visibility memo poisoned");
777            if let Some(existing) = entries.get(key).cloned() {
778                return Ok(existing);
779            }
780            if entries.len() < capacity {
781                entries.insert(key.clone(), Arc::clone(&value));
782            }
783            Ok(value)
784        })
785        .expect("infallible bounded C++ visibility build");
786    (*value).clone()
787}
788
789fn visibility_once_value<T: Clone>(
790    memo: &VisibilityOnce<T>,
791    reads: &ReadCapability,
792    build: impl FnOnce() -> T,
793) -> T {
794    let value = memo
795        .read(&reads.memo_attribution(), |cell, capture| {
796            Ok::<_, std::convert::Infallible>(Arc::clone(
797                cell.get_or_init(|| Arc::new(capture.build(build))),
798            ))
799        })
800        .expect("infallible C++ visibility cell build");
801    (*value).clone()
802}
803
804pub struct VisibilityCell<T> {
805    memo: VisibilityOnce<T>,
806    reads: ReadCapability,
807}
808impl<T: Clone> VisibilityCell<T> {
809    fn new(reads: ReadCapability) -> Self {
810        Self {
811            memo: VisibilityOnce::default(),
812            reads,
813        }
814    }
815    pub fn get_or_init(&self, build: impl FnOnce() -> T) -> T {
816        visibility_once_value(&self.memo, &self.reads, build)
817    }
818    pub fn get(&self) -> Option<T> {
819        self.memo
820            .read(&self.reads.memo_attribution(), |cell, _| {
821                cell.get().cloned().ok_or(())
822            })
823            .ok()
824            .map(|value| (*value).clone())
825    }
826}
827
828type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
829pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
830pub type MacroEventCell = Arc<VisibilityCell<Arc<[MacroEvent]>>>;
831type MacroEnvironmentCheckpointCell = Arc<VisibilityCell<Arc<MacroEnvironmentCheckpoints>>>;
832
833struct StructuredIncludeFact {
834    start_byte: usize,
835    end_byte: usize,
836    path: String,
837}
838
839#[derive(Clone, Default)]
840pub struct MacroEnvironment {
841    bindings: HashMap<String, MacroBinding>,
842    known_undefined_names: HashSet<String>,
843    /// Names the translation unit's compile command proves defined (#2011):
844    /// the `-D`s that survive command ordering, intersected across every
845    /// configuration naming the TU. Seeded once at TU start. An explicit
846    /// `#undef` seen later lands in `known_undefined_names` and wins.
847    build_proven_defines: HashSet<String>,
848    unknown_names: bool,
849    applied_pragma_once_files: HashSet<ProjectFile>,
850    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
851}
852
853/// How many macro events one checkpoint window may cover.
854///
855/// A request for an environment replays only the events between the nearest
856/// earlier checkpoint and its own frontier, so one file's whole scan costs its
857/// event count (the checkpoint build) plus this many applications per request,
858/// whatever order the requests arrive in. The forward cursor this replaced was
859/// optimal for one worker reading one file in byte order and quadratic for the
860/// inverse, which asks many workers for positions that move backwards (#1496).
861pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
862
863/// One event prefix of a file whose environment the index keeps.
864struct MacroEnvironmentCheckpoint {
865    /// How many of the file's events this environment has applied.
866    frontier: usize,
867    environment: Arc<MacroEnvironment>,
868}
869
870/// The replay checkpoints for one file's macro events, ascending by frontier
871/// and always starting at frontier zero (the compile-proven defines alone).
872///
873/// Checkpoints use an adaptive stride no greater than
874/// [`MACRO_ENVIRONMENT_CHECKPOINT_STRIDE`], and one lands directly after every
875/// `#include` event. Applying an include event replays the included file's
876/// complete event list, so keeping one there holds that unbounded cost out of
877/// every later replay window.
878struct MacroEnvironmentCheckpoints {
879    checkpoints: Vec<MacroEnvironmentCheckpoint>,
880}
881
882impl MacroEnvironmentCheckpoints {
883    /// The latest checkpoint at or before `frontier`.
884    fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
885        let index = self
886            .checkpoints
887            .partition_point(|checkpoint| checkpoint.frontier <= frontier);
888        assert!(
889            index > 0,
890            "a checkpoint vector starts at frontier zero, which precedes every request"
891        );
892        &self.checkpoints[index - 1]
893    }
894}
895
896impl MacroEnvironment {
897    fn binding(&self, name: &str) -> Option<&MacroBinding> {
898        self.bindings.get(name)
899    }
900
901    fn may_bind(&self, name: &str) -> bool {
902        self.bindings.contains_key(name) || self.unknown_names
903    }
904
905    fn insert(&mut self, name: String, binding: MacroBinding) {
906        self.known_undefined_names.remove(&name);
907        self.bindings.insert(name, binding);
908    }
909
910    fn remove(&mut self, name: &str) {
911        self.bindings.remove(name);
912        self.known_undefined_names.insert(name.to_string());
913    }
914
915    fn remove_known_undefined(&mut self, name: &str) {
916        self.known_undefined_names.remove(name);
917    }
918
919    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
920        for binding in self.bindings.values_mut() {
921            *binding = MacroBinding::uncertain_from(binding, source, byte);
922        }
923        self.known_undefined_names.clear();
924        // An untracked include could `#undef` a command-line define, so the
925        // may-hold filter must stop treating the build facts as decisive from
926        // here on. The additive proof path keeps its facts: they still hold at
927        // the include chain's activation point.
928        self.build_proven_defines.clear();
929        self.unknown_names = true;
930    }
931
932    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
933        guards.iter().all(|guard| self.guard_may_hold(guard))
934    }
935
936    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
937        self.boolean_guard_may_hold(&guard.as_boolean_expression())
938    }
939
940    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
941        match expression {
942            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
943            BooleanGuardExpression::Undefined(name) => {
944                self.bindings
945                    .get(name)
946                    .is_none_or(|binding| !binding.is_exact())
947                    && (!self.build_proven_defines.contains(name)
948                        || self.known_undefined_names.contains(name))
949            }
950            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
951            BooleanGuardExpression::Opaque(_)
952            | BooleanGuardExpression::NegatedOpaque(_)
953            | BooleanGuardExpression::Constant(true) => true,
954            BooleanGuardExpression::Constant(false) => false,
955            BooleanGuardExpression::All(expressions) => expressions
956                .iter()
957                .all(|expression| self.boolean_guard_may_hold(expression)),
958            BooleanGuardExpression::Any(expressions) => expressions
959                .iter()
960                .any(|expression| self.boolean_guard_may_hold(expression)),
961        }
962    }
963}
964
965fn any_visible_source_file_matches(
966    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
967    file: &ProjectFile,
968    mut predicate: impl FnMut(&ProjectFile) -> bool,
969) -> bool {
970    match visible_source_files_by_root.get(file) {
971        Some(files) => files.iter().any(predicate),
972        None => predicate(file),
973    }
974}
975
976#[derive(Clone)]
977pub enum EffectiveUsingTarget {
978    Ordinary {
979        name: String,
980        target_components: Vec<String>,
981        global: bool,
982    },
983    Namespace {
984        namespace_components: Vec<String>,
985        global: bool,
986    },
987}
988
989#[derive(Clone)]
990pub struct OrdinaryTypeImport {
991    pub target: EffectiveUsingTarget,
992    pub source: ProjectFile,
993    pub declaration_byte: usize,
994    pub scope_start: usize,
995    pub scope_end: usize,
996    pub scope_depth: usize,
997    pub block_scope: bool,
998    pub lexical_depth: usize,
999    pub declaration_namespace: Vec<String>,
1000    pub namespace_scope: Option<Vec<String>>,
1001    pub resolved_target_components: Option<Vec<String>>,
1002    pub required_guards: HashSet<PreprocessorGuard>,
1003}
1004
1005#[derive(Clone)]
1006pub struct ConditionalIncludeProjection {
1007    pub activation_byte: usize,
1008    pub required_guards: HashSet<PreprocessorGuard>,
1009    /// The subset of `required_guards` a lone `#if` contributes: a branch no
1010    /// sibling `#else` completes, so no configuration is obliged to take it.
1011    /// [`IncludePathAdmission::Compatible`] still demands those.
1012    pub partial_guards: HashSet<PreprocessorGuard>,
1013}
1014
1015#[derive(Default)]
1016pub struct SourceUsingIndex {
1017    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
1018    pub directives: Vec<OrdinaryTypeImport>,
1019}
1020
1021#[derive(Default)]
1022pub struct ProjectUsingIndex {
1023    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
1024    pub directives: Vec<OrdinaryTypeImport>,
1025}
1026
1027type EffectiveUsingProjectionCell = Arc<VisibilityCell<Arc<[OrdinaryTypeImport]>>>;
1028
1029pub struct EffectiveUsingIndex {
1030    reads: ReadCapability,
1031    projected_by_name: VisibilityMemo<String, EffectiveUsingProjectionCell>,
1032}
1033
1034impl EffectiveUsingIndex {
1035    fn new(_root: ProjectFile, reads: ReadCapability) -> Self {
1036        Self {
1037            reads,
1038            projected_by_name: VisibilityMemo::default(),
1039        }
1040    }
1041
1042    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
1043        visibility_memo_value(
1044            &self.projected_by_name,
1045            &self.reads,
1046            name.to_owned(),
1047            || Arc::new(VisibilityCell::new(self.reads.clone())),
1048        )
1049    }
1050}
1051
1052pub enum OrdinaryTypeImportResolution {
1053    Resolved {
1054        target: CodeUnit,
1055        target_components: Vec<String>,
1056        lexical_depth: usize,
1057        is_direct: bool,
1058    },
1059    Ambiguous {
1060        lexical_depth: usize,
1061    },
1062    Missing,
1063}
1064
1065type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
1066
1067/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
1068/// read from its declaration syntax rather than from its persisted signature
1069/// string: the comparable shape of each parameter, and the trailing identity
1070/// suffix that shape does not carry.
1071struct ExtractedComparable {
1072    shapes: Vec<CppComparableSlot>,
1073    suffix: String,
1074    /// The template levels of the same occurrence, or `None` when its syntax
1075    /// is unavailable or outside what [`crate::template_heads`] models.
1076    template: Option<CppTemplateContext>,
1077}
1078
1079/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
1080/// before giving up on a written type name. A visited set already stops a
1081/// cycle; this stops an adversarially long chain from costing a lookup per hop.
1082const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
1083
1084/// Guard admission for a conditional include path. Exact visibility
1085/// (`foreign_declaration_reachable_at_reference`) always uses `Proven`: the
1086/// effective source and build context must imply the path. Candidate discovery
1087/// (`foreign_declaration_may_be_reachable_from_raw_guards`) takes
1088/// `include_path_admission(file)`, which is `Compatible` for completed families
1089/// when no compile coverage describes the file; partial-family requirements
1090/// still need proof. Discovery alone never establishes an exact definition or
1091/// a proven inverse hit (#6902).
1092#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1093enum IncludePathAdmission {
1094    Proven,
1095    Compatible,
1096}
1097
1098impl IncludePathAdmission {
1099    fn admits(
1100        self,
1101        required: &HashSet<PreprocessorGuard>,
1102        partial: &HashSet<PreprocessorGuard>,
1103        reference_guards: Option<&HashSet<PreprocessorGuard>>,
1104    ) -> bool {
1105        match self {
1106            Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
1107            Self::Compatible => {
1108                guard_requirements_hold_at_reference(partial, reference_guards)
1109                    && guards_compatible_at_reference(required, reference_guards)
1110            }
1111        }
1112    }
1113}
1114
1115/// Per-query C++ visibility facts with a borrowed source and scope proof.
1116/// Cache only `data()` across calls, and reattach the current source and token
1117/// with `from_data`; cloning the analyzer would lose its query read cache.
1118pub struct VisibilityIndex<'a> {
1119    cpp: &'a dyn CppSource,
1120    token: QueryToken<'a>,
1121    data: Arc<VisibilityIndexData>,
1122}
1123
1124impl std::ops::Deref for VisibilityIndex<'_> {
1125    type Target = VisibilityIndexData;
1126
1127    fn deref(&self) -> &Self::Target {
1128        &self.data
1129    }
1130}
1131
1132/// Owned visibility facts. They retain no source or request-scope borrow.
1133/// The caller keeps these facts inside the query that built them.
1134pub struct VisibilityIndexData {
1135    pub(crate) reads: ReadCapability,
1136    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1137    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
1138    global_field_internal_linkage: HashMap<CodeUnit, bool>,
1139    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
1140    alias_cells: VisibilityMemo<ProjectFile, AliasCell>,
1141    visible_parser_alias_name_sets: VisibilityMemo<ProjectFile, Arc<HashSet<String>>>,
1142    parser_alias_target_matches: VisibilityMemo<(ProjectFile, String, LogicalSymbolKey), bool>,
1143    ordinary_type_import_cells: VisibilityMemo<ProjectFile, OrdinaryTypeImportCell>,
1144    project_using_index: VisibilityOnce<Arc<ProjectUsingIndex>>,
1145    namespace_alias_sites: VisibilityMemo<ProjectFile, super::namespace_alias::NamespaceAliasCell>,
1146    callable_reference_specs:
1147        VisibilityMemo<(ProjectFile, LogicalSymbolKey), Option<Arc<TargetSpec>>>,
1148    structured_include_fact_cells: VisibilityMemo<ProjectFile, Arc<[StructuredIncludeFact]>>,
1149    include_activation_cells: VisibilityMemo<(ProjectFile, ProjectFile), Option<usize>>,
1150    compile_proven_guard_cells: VisibilityMemo<ProjectFile, Arc<HashSet<PreprocessorGuard>>>,
1151    include_path_admission_cells: VisibilityMemo<ProjectFile, IncludePathAdmission>,
1152    conditional_include_projection_cells:
1153        VisibilityMemo<ProjectFile, Arc<ConditionalIncludeProjectionIndex>>,
1154    #[cfg(any(test, feature = "test-support"))]
1155    conditional_include_projection_index_build_count: AtomicUsize,
1156    #[cfg(any(test, feature = "test-support"))]
1157    conditional_include_projection_state_count: AtomicUsize,
1158    #[cfg(any(test, feature = "test-support"))]
1159    conditional_include_target_state_count: AtomicUsize,
1160    #[cfg(any(test, feature = "test-support"))]
1161    include_activation_build_count: AtomicUsize,
1162    #[cfg(any(test, feature = "test-support"))]
1163    using_donor_activation_count: AtomicUsize,
1164    #[cfg(any(test, feature = "test-support"))]
1165    using_namespace_lookup_count: AtomicUsize,
1166    #[cfg(any(test, feature = "test-support"))]
1167    using_name_candidate_inspection_count: AtomicUsize,
1168    #[cfg(any(test, feature = "test-support"))]
1169    callable_reference_spec_build_count: AtomicUsize,
1170    #[cfg(any(test, feature = "test-support"))]
1171    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1172    #[cfg(any(test, feature = "test-support"))]
1173    visible_parser_alias_name_set_build_count: AtomicUsize,
1174    parser_alias_fallback_calls: AtomicUsize,
1175    parser_alias_fallback_files: AtomicUsize,
1176    parser_alias_source_parses: AtomicUsize,
1177    parser_alias_fallback_elapsed_micros: AtomicUsize,
1178    field_type_facts: VisibilityMemo<CodeUnit, Option<DeclaredFieldTypeFact>>,
1179    structured_alias_targets: VisibilityMemo<CodeUnit, Option<StructuredAliasTarget>>,
1180    callable_comparables: VisibilityMemo<CodeUnit, Option<Arc<ExtractedComparable>>>,
1181    class_definition_agreements: VisibilityMemo<(CodeUnit, CodeUnit), bool>,
1182    /// Complete callable metadata is expensive for reconciled C++ members:
1183    /// each read can rebuild the declaration/definition evidence. Reuse it
1184    /// across the member, arity, identity, and navigation phases of this
1185    /// visibility query, with an explicit cap so the query never retains a
1186    /// workspace-sized unit table.
1187    callable_metadata: BoundedVisibilityMemo<CodeUnit, Arc<Vec<SignatureMetadata>>>,
1188    comparable_name_declarations: VisibilityMemo<StructuredTypeName, Option<CodeUnit>>,
1189    indexed_structural_class_scopes:
1190        VisibilityMemo<(ProjectFile, usize, usize), Option<Vec<String>>>,
1191    indexed_enclosing_owner_scopes:
1192        VisibilityMemo<(ProjectFile, usize, usize), Option<Vec<String>>>,
1193    precise_parent_cache: VisibilityMemo<CodeUnit, Option<CodeUnit>>,
1194    c_tag_kind_cache: VisibilityMemo<CodeUnit, Option<CppCTagKind>>,
1195    c_tag_complete_definition_cache: VisibilityMemo<CodeUnit, Option<CodeUnit>>,
1196    macro_event_cells: VisibilityMemo<ProjectFile, MacroEventCell>,
1197    macro_event_name_sets: VisibilityMemo<ProjectFile, Arc<HashSet<String>>>,
1198    pub macro_include_protection_cells: IncludeProtectionMemo,
1199    // The environment at selected event prefixes of each file, built once and read by every
1200    // worker. The authoritative differential shares this index across target workers whose
1201    // frontiers interleave arbitrarily and move backwards, so a forward cursor -- per worker or
1202    // not -- replayed a file's events once per backward request. Checkpoints answer any position
1203    // with a binary search and at most one stride of replay, and being immutable they need no
1204    // per-worker copy (#1496).
1205    macro_environment_checkpoints: VisibilityMemo<ProjectFile, MacroEnvironmentCheckpointCell>,
1206    macro_replacements: VisibilityMemo<(ProjectFile, usize), Arc<ParsedMacroReplacement>>,
1207    macro_local_binding_templates:
1208        VisibilityMemo<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>,
1209    pub(crate) macro_lexical_templates:
1210        VisibilityMemo<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>,
1211    macro_lexical_indexes:
1212        BoundedVisibilityMemo<ProjectFile, Arc<crate::graph::macro_lexical::MacroLexicalIndex>>,
1213    macro_replacement_bodies:
1214        VisibilityMemo<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>,
1215    macro_type_parameters: VisibilityMemo<(ProjectFile, usize), Option<Arc<[usize]>>>,
1216    callable_parameter_macro_arities: VisibilityMemo<(ProjectFile, String), Option<CallableArity>>,
1217    #[cfg(any(test, feature = "test-support"))]
1218    pub macro_replacement_parse_count: AtomicUsize,
1219    #[cfg(any(test, feature = "test-support"))]
1220    pub macro_event_application_count: AtomicUsize,
1221    /// How many files had their checkpoint vector built. One build per file
1222    /// per query even when workers race for the same file.
1223    #[cfg(any(test, feature = "test-support"))]
1224    pub macro_environment_checkpoint_build_count: AtomicUsize,
1225    /// How many requests landed off a checkpoint and so had to copy one and
1226    /// replay the events after it.
1227    #[cfg(any(test, feature = "test-support"))]
1228    pub macro_environment_copy_count: AtomicUsize,
1229    #[cfg(any(test, feature = "test-support"))]
1230    pub macro_environment_request_count: AtomicUsize,
1231    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1232    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1233    #[cfg(any(test, feature = "test-support"))]
1234    qualified_candidate_inspections: AtomicUsize,
1235    #[cfg(any(test, feature = "test-support"))]
1236    target_preserving_type_resolution_count: AtomicUsize,
1237    #[cfg(any(test, feature = "test-support"))]
1238    visibility_identifier_lookup_count: usize,
1239    #[cfg(any(test, feature = "test-support"))]
1240    visibility_identifier_batch_count: usize,
1241}
1242
1243impl Drop for VisibilityIndexData {
1244    fn drop(&mut self) {
1245        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1246            return;
1247        }
1248        #[cfg(any(test, feature = "test-support"))]
1249        eprintln!(
1250            "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1251            self.macro_environment_request_count.load(Ordering::Relaxed),
1252            self.macro_environment_copy_count.load(Ordering::Relaxed),
1253            self.macro_environment_checkpoint_build_count
1254                .load(Ordering::Relaxed),
1255            self.macro_event_application_count.load(Ordering::Relaxed),
1256        );
1257        let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1258        if calls == 0 {
1259            return;
1260        }
1261        eprintln!(
1262            "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1263            calls,
1264            self.parser_alias_fallback_files.load(Ordering::Relaxed),
1265            self.parser_alias_source_parses.load(Ordering::Relaxed),
1266            self.parser_alias_fallback_elapsed_micros
1267                .load(Ordering::Relaxed)
1268                / 1_000,
1269        );
1270    }
1271}
1272
1273#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1274pub enum PreprocessorGuard {
1275    Defined(String),
1276    Undefined(String),
1277    Boolean(BooleanGuardExpression),
1278    Expression(String),
1279    NegatedExpression(String),
1280    Constant(bool),
1281}
1282
1283#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1284pub enum BooleanGuardExpression {
1285    Defined(String),
1286    Undefined(String),
1287    Truthy(String),
1288    Falsy(String),
1289    Opaque(String),
1290    NegatedOpaque(String),
1291    All(Vec<BooleanGuardExpression>),
1292    Any(Vec<BooleanGuardExpression>),
1293    Constant(bool),
1294}
1295
1296impl BooleanGuardExpression {
1297    fn negated(&self) -> Self {
1298        match self {
1299            Self::Defined(name) => Self::Undefined(name.clone()),
1300            Self::Undefined(name) => Self::Defined(name.clone()),
1301            Self::Truthy(name) => Self::Falsy(name.clone()),
1302            Self::Falsy(name) => Self::Truthy(name.clone()),
1303            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1304            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1305            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1306            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1307            Self::Constant(value) => Self::Constant(!value),
1308        }
1309    }
1310
1311    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1312        Self::normalized(expressions, true)
1313    }
1314
1315    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1316        Self::normalized(expressions, false)
1317    }
1318
1319    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1320        let mut normalized = Vec::new();
1321        for expression in expressions {
1322            match expression {
1323                Self::All(nested) if conjunction => normalized.extend(nested),
1324                Self::Any(nested) if !conjunction => normalized.extend(nested),
1325                Self::Constant(value) if value == conjunction => {}
1326                Self::Constant(value) => return Self::Constant(value),
1327                expression => normalized.push(expression),
1328            }
1329        }
1330        normalized.sort_unstable();
1331        normalized.dedup();
1332        match normalized.len() {
1333            0 => Self::Constant(conjunction),
1334            1 => normalized.pop().expect("one Boolean guard expression"),
1335            _ if conjunction => Self::All(normalized),
1336            _ => Self::Any(normalized),
1337        }
1338    }
1339
1340    fn implies(&self, required: &Self) -> bool {
1341        if self == required
1342            || matches!(self, Self::Constant(false))
1343            || matches!(required, Self::Constant(true))
1344        {
1345            return true;
1346        }
1347        if matches!(
1348            (self, required),
1349            (Self::Truthy(active), Self::Defined(required))
1350                | (Self::Undefined(active), Self::Falsy(required))
1351                if active == required
1352        ) {
1353            return true;
1354        }
1355        match self {
1356            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1357            Self::All(active) => match required {
1358                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1359                _ => active.iter().any(|expression| expression.implies(required)),
1360            },
1361            _ => match required {
1362                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1363                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1364                _ => false,
1365            },
1366        }
1367    }
1368
1369    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1370        match self {
1371            Self::Defined(name)
1372            | Self::Undefined(name)
1373            | Self::Truthy(name)
1374            | Self::Falsy(name) => name == macro_name,
1375            // Opaque expressions have structured conditional ownership but no
1376            // structured macro operands, so any mutation may change them.
1377            Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1378            Self::All(expressions) | Self::Any(expressions) => expressions
1379                .iter()
1380                .any(|expression| expression.may_depend_on_macro(macro_name)),
1381            Self::Constant(_) => false,
1382        }
1383    }
1384
1385    pub fn heap_size(&self) -> usize {
1386        match self {
1387            Self::Defined(value)
1388            | Self::Undefined(value)
1389            | Self::Truthy(value)
1390            | Self::Falsy(value)
1391            | Self::Opaque(value)
1392            | Self::NegatedOpaque(value) => value.len(),
1393            Self::All(expressions) | Self::Any(expressions) => {
1394                expressions
1395                    .iter()
1396                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1397                        size.saturating_add(std::mem::size_of::<Self>())
1398                            .saturating_add(expression.heap_size())
1399                    })
1400            }
1401            Self::Constant(_) => 0,
1402        }
1403    }
1404}
1405
1406impl PreprocessorGuard {
1407    fn as_boolean_expression(&self) -> BooleanGuardExpression {
1408        match self {
1409            Self::Defined(name) => BooleanGuardExpression::Defined(name.clone()),
1410            Self::Undefined(name) => BooleanGuardExpression::Undefined(name.clone()),
1411            Self::Boolean(expression) => expression.clone(),
1412            Self::Constant(value) => BooleanGuardExpression::Constant(*value),
1413            Self::Expression(expression) => BooleanGuardExpression::Opaque(expression.clone()),
1414            Self::NegatedExpression(expression) => {
1415                BooleanGuardExpression::NegatedOpaque(expression.clone())
1416            }
1417        }
1418    }
1419
1420    fn negated(&self) -> Self {
1421        match self {
1422            Self::Defined(name) => Self::Undefined(name.clone()),
1423            Self::Undefined(name) => Self::Defined(name.clone()),
1424            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1425            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1426            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1427            Self::Constant(value) => Self::Constant(!value),
1428        }
1429    }
1430
1431    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1432        match self {
1433            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1434            Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1435            // These expressions could not be lowered to a Boolean operand
1436            // tree, so their dependencies remain unknown.
1437            Self::Expression(_) | Self::NegatedExpression(_) => true,
1438            Self::Constant(_) => false,
1439        }
1440    }
1441}
1442
1443#[derive(Clone, PartialEq, Eq)]
1444pub enum MacroDefinition {
1445    Object {
1446        replacement: String,
1447    },
1448    Function {
1449        parameters: Vec<String>,
1450        replacement: String,
1451    },
1452    VariadicFunction {
1453        parameters: Vec<String>,
1454        replacement: String,
1455    },
1456    Unsupported,
1457}
1458
1459#[derive(Clone, Debug, PartialEq, Eq)]
1460pub enum MacroIncludeProtection {
1461    MacroGuard(String),
1462    PragmaOnce,
1463    None,
1464}
1465
1466enum ParsedMacroReplacement {
1467    Parsed { source: String, tree: Tree },
1468    Unsupported,
1469}
1470
1471/// The sentinel that gives a function-like macro replacement a parseable
1472/// statement context after phase-2 splicing. The only bytes ahead of it are
1473/// this prefix.
1474const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1475const MACRO_BODY_SENTINEL_SUFFIX: &str = "; }";
1476
1477// Each parsed byte owns its physical source byte, or an empty range for
1478// synthetic punctuation. Deleted splices own no parsed bytes.
1479type MacroOriginMap = Box<[std::ops::Range<usize>]>;
1480
1481/// A function-like macro replacement parsed inside a sentinel function body.
1482///
1483/// Tree-sitter keeps a `#define NAME(a) ...` replacement as one opaque
1484/// `preproc_arg`. Wrapping that exact byte slice in a function body recovers
1485/// its statements, declarations, and member calls as ordinary C++ structure.
1486/// The spliced text begins at [`Self::body_offset`]. [`Self::file_range`]
1487/// subtracts that prefix and uses physical byte origins to recover a node
1488/// range in the defining replacement.
1489pub struct ParsedReplacementBody {
1490    pub source: String,
1491    pub tree: Tree,
1492    pub body_offset: usize,
1493    pub parameters: Vec<String>,
1494    /// Each parsed replacement byte maps to its original physical byte. A
1495    /// deleted splice creates a gap; a synthetic statement separator maps to
1496    /// an empty range, keeping source-backed ranges exact after substitution.
1497    original_offsets: MacroOriginMap,
1498}
1499
1500impl ParsedReplacementBody {
1501    /// The sentinel function body holding the replacement's statements.
1502    pub fn statements(&self) -> Option<Node<'_>> {
1503        first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1504            .child_by_field_name("body")
1505    }
1506
1507    /// The byte range `node` occupies in the file that defines the macro.
1508    ///
1509    /// `replacement_start` is the logical replacement span's start byte.
1510    /// The origin map accounts for deleted continuations and any synthetic
1511    /// statement separators inserted while parsing the replacement.
1512    pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1513        assert!(
1514            node.start_byte() >= self.body_offset,
1515            "synthetic sentinel node cannot be mapped to a macro replacement"
1516        );
1517        assert!(
1518            node.end_byte() >= self.body_offset,
1519            "synthetic sentinel node cannot be mapped to a macro replacement"
1520        );
1521        let start_offset = node.start_byte() - self.body_offset;
1522        let end_offset = node.end_byte() - self.body_offset;
1523        assert!(start_offset <= end_offset);
1524        let start_origin = self
1525            .original_offsets
1526            .get(start_offset)
1527            .expect("replacement node start must have a source mapping")
1528            .start;
1529        let end_origin = if start_offset == end_offset {
1530            start_origin
1531        } else {
1532            self.original_offsets
1533                .get(end_offset - 1)
1534                .expect("replacement node end must have a source mapping")
1535                .end
1536        };
1537        assert!(start_origin <= end_origin);
1538        let start = replacement_start + start_origin;
1539        let end = replacement_start + end_origin;
1540        start..end
1541    }
1542
1543    /// Whether the replacement names the variadic argument pack.
1544    ///
1545    /// `__VA_ARGS__` parses as an ordinary identifier, so the sentinel tree
1546    /// gives no error for it even though the expansion it stands for is
1547    /// unknown at the definition. Reject it from the parsed tree rather than
1548    /// by scanning the replacement text.
1549    fn expands_variadic_arguments(&self) -> bool {
1550        let mut stack = vec![self.tree.root_node()];
1551        while let Some(node) = stack.pop() {
1552            if matches!(
1553                node.kind(),
1554                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1555            ) && node_text(node, &self.source) == "__VA_ARGS__"
1556            {
1557                return true;
1558            }
1559            push_named_children_reversed(node, &mut stack);
1560        }
1561        false
1562    }
1563}
1564
1565/// Parse the value of a C++ integer-literal token when it fits in `i128`.
1566pub fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1567    let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1568    let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1569        if compact.starts_with("0x") || compact.starts_with("0X") {
1570            (16, 2, |ch| ch.is_ascii_hexdigit())
1571        } else if compact.starts_with("0b") || compact.starts_with("0B") {
1572            (2, 2, |ch| matches!(ch, '0' | '1'))
1573        } else if compact.starts_with('0') && compact.len() > 1 {
1574            (8, 0, |ch| matches!(ch, '0'..='7'))
1575        } else {
1576            (10, 0, |ch| ch.is_ascii_digit())
1577        };
1578    let digit_len = compact[digits_start..]
1579        .chars()
1580        .take_while(|ch| digit_matches(*ch))
1581        .map(char::len_utf8)
1582        .sum::<usize>();
1583    if digit_len == 0 {
1584        return None;
1585    }
1586    let digits_end = digits_start + digit_len;
1587    if !compact[digits_end..]
1588        .chars()
1589        .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1590    {
1591        return None;
1592    }
1593    i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1594}
1595
1596#[derive(Clone)]
1597enum MacroLocalBindingTypeTemplate {
1598    Parameter(usize),
1599    Fixed(String),
1600}
1601
1602#[derive(Clone)]
1603struct MacroLocalBindingTemplate {
1604    name: String,
1605    declared_type: MacroLocalBindingTypeTemplate,
1606    pointer_depth: i32,
1607}
1608
1609/// A local declaration contributed by one structurally known function-like macro.
1610///
1611/// `type_node` points into the invocation syntax when the replacement's type
1612/// is one of the macro parameters. Consumers can therefore use their normal
1613/// lexical type resolver without parsing replacement text themselves.
1614/// `proven_unit` carries a structured C-tag resolution when the grammar splits
1615/// an explicit `struct` or `union` argument across recovery nodes.
1616pub struct MacroLocalBinding<'tree> {
1617    pub name: String,
1618    pub type_name: String,
1619    pub type_node: Option<Node<'tree>>,
1620    pub pointer_depth: i32,
1621    pub proven_unit: Option<CodeUnit>,
1622}
1623
1624/// The source-backed declaration introduced by a function-like macro. The
1625/// declaration belongs to the macro definition, while the reference range
1626/// returned by [`VisibilityIndex::macro_lexical_references`] belongs to the
1627/// definition body, invocation argument, or caller scope where substitution
1628/// makes the binding visible.
1629#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1630pub enum MacroLexicalBindingKind {
1631    Parameter,
1632    Local,
1633}
1634
1635#[derive(Clone, Debug, PartialEq, Eq)]
1636pub struct MacroLexicalBinding {
1637    pub definition: ProjectFile,
1638    pub kind: MacroLexicalBindingKind,
1639    pub name: String,
1640    pub name_range: std::ops::Range<usize>,
1641    pub declaration_range: std::ops::Range<usize>,
1642}
1643
1644/// Lexical macro references are query-local facts. A cancelled or bounded
1645/// walk is kept distinct from a complete empty enumeration so authoritative
1646/// inverse callers can preserve their completeness status.
1647#[derive(Clone, Debug, Default, PartialEq, Eq)]
1648pub struct MacroLexicalReferences {
1649    pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1650    pub truncated: bool,
1651    pub cancelled: bool,
1652}
1653
1654fn macro_replacement_type_parameters(
1655    body: &ParsedReplacementBody,
1656    parameters: &[String],
1657) -> Option<Vec<usize>> {
1658    let mut found = Vec::new();
1659    let mut stack = vec![body.tree.root_node()];
1660    while let Some(node) = stack.pop() {
1661        let type_position = node.kind() == "type_identifier"
1662            || (node.kind() == "identifier"
1663                && node.parent().is_some_and(|parent| {
1664                    parent.kind() == "type_descriptor"
1665                        && parent.child_by_field_name("type") == Some(node)
1666                }));
1667        let offsetof_type_position = node.kind() == "identifier"
1668            && node
1669                .parent()
1670                .filter(|parent| parent.kind() == "argument_list")
1671                .and_then(|arguments| arguments.parent())
1672                .is_some_and(|call| {
1673                    call.kind() == "call_expression"
1674                        && call
1675                            .child_by_field_name("function")
1676                            .is_some_and(|function| {
1677                                function.kind() == "identifier"
1678                                    && node_text(function, &body.source) == "offsetof"
1679                            })
1680                        && call
1681                            .child_by_field_name("arguments")
1682                            .is_some_and(|arguments| {
1683                                argument_children(arguments).next() == Some(node)
1684                            })
1685                });
1686        if (type_position || offsetof_type_position)
1687            && let Some(index) = parameters
1688                .iter()
1689                .position(|parameter| parameter == node_text(node, &body.source))
1690            && !found.contains(&index)
1691        {
1692            found.push(index);
1693        }
1694        push_named_children_reversed(node, &mut stack);
1695    }
1696    (!found.is_empty()).then_some(found)
1697}
1698
1699fn macro_replacement_type_parameter(
1700    body: &ParsedReplacementBody,
1701    parameters: &[String],
1702) -> Option<usize> {
1703    let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1704    (parameters.len() == 1).then(|| parameters.pop().unwrap())
1705}
1706
1707/// The grammar retains a type-shaped macro invocation as a macro type
1708/// specifier even when it stands alone in a C block. Its descriptor is one
1709/// actual argument, just as the argument list of an expression-shaped call is.
1710pub(crate) fn function_macro_invocation_parts(node: Node<'_>) -> Option<(Node<'_>, Vec<Node<'_>>)> {
1711    let (name, arguments) = match node.kind() {
1712        "call_expression" => (
1713            node.child_by_field_name("function")?,
1714            argument_children(node.child_by_field_name("arguments")?).collect(),
1715        ),
1716        "macro_type_specifier" => (
1717            node.child_by_field_name("name")?,
1718            vec![node.child_by_field_name("type")?],
1719        ),
1720        _ => return None,
1721    };
1722    (name.kind() == "identifier").then_some((name, arguments))
1723}
1724
1725pub(crate) fn macro_type_argument_node<'tree>(
1726    node: Node<'tree>,
1727    source: &str,
1728) -> Option<Node<'tree>> {
1729    match node.kind() {
1730        "type_descriptor" => {
1731            let type_child = node
1732                .child_by_field_name("type")
1733                .or_else(|| first_type_child(node))?;
1734            for index in (0..node.named_child_count()).rev() {
1735                let child = node.named_child(index)?;
1736                if child != type_child
1737                    && matches!(
1738                        child.kind(),
1739                        "identifier"
1740                            | "type_identifier"
1741                            | "qualified_identifier"
1742                            | "scoped_type_identifier"
1743                    )
1744                {
1745                    return Some(child);
1746                }
1747            }
1748            macro_type_argument_node(type_child, source)
1749        }
1750        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1751            node.child_by_field_name("name")
1752        }
1753        "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1754            .next_named_sibling()
1755            .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1756            .and_then(|error| error.named_child(0))
1757            .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1758        _ => cpp_name_component_nodes(node).is_some().then_some(node),
1759    }
1760}
1761
1762fn c_function_macro_argument<'tree>(
1763    node: Node<'tree>,
1764) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1765    let mut current = node;
1766    while let Some(parent) = current.parent() {
1767        if parent.kind() == "argument_list" {
1768            let call = parent.parent().filter(|call| {
1769                call.kind() == "call_expression"
1770                    && call.child_by_field_name("arguments") == Some(parent)
1771                    && call
1772                        .child_by_field_name("function")
1773                        .is_some_and(|function| {
1774                            function.kind() == "identifier"
1775                                && !node_range_contains(function, current)
1776                        })
1777            })?;
1778            let mut actuals = argument_children(parent).enumerate();
1779            let (index, argument) = actuals.find(|(_, argument)| {
1780                node_range_contains(*argument, node)
1781                    && (argument.start_byte() == current.start_byte()
1782                        || argument.end_byte() == current.end_byte())
1783            })?;
1784            return Some((call, index, argument));
1785        }
1786        current = parent;
1787    }
1788    None
1789}
1790
1791/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1792/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1793/// invocation as the assignment's left operand and the declared name as one
1794/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1795/// The macro-declaration grammar reads the same line as a clean declaration
1796/// whose type is a `macro_type_specifier`; the `g_autoptr` name gate is what
1797/// keeps an arbitrary function-like type macro from becoming a binding.
1798fn recognized_c_macro_declarator_binding<'tree>(
1799    statement: Node<'tree>,
1800    source: &str,
1801) -> Option<MacroLocalBinding<'tree>> {
1802    if statement.kind() == "declaration" {
1803        let specifier = statement
1804            .child_by_field_name("type")
1805            .filter(|type_node| type_node.kind() == "macro_type_specifier")?;
1806        let macro_name = specifier
1807            .child_by_field_name("name")
1808            .filter(|name| name.kind() == "identifier")?;
1809        if node_text(macro_name, source) != "g_autoptr" {
1810            return None;
1811        }
1812        let type_node = specifier
1813            .child_by_field_name("type")
1814            .and_then(|descriptor| descriptor.child_by_field_name("type"))?;
1815        let declarator = statement
1816            .child_by_field_name("declarator")
1817            .filter(|declarator| declarator.kind() == "init_declarator")?;
1818        let name_node = declarator
1819            .child_by_field_name("declarator")
1820            .filter(|name| name.kind() == "identifier")?;
1821        declarator.child_by_field_name("value")?;
1822        let name = node_text(name_node, source).trim();
1823        let type_name = node_text(type_node, source).trim();
1824        if name.is_empty() || type_name.is_empty() {
1825            return None;
1826        }
1827        return Some(MacroLocalBinding {
1828            name: name.to_string(),
1829            type_name: type_name.to_string(),
1830            type_node: Some(type_node),
1831            pointer_depth: 1,
1832            proven_unit: None,
1833        });
1834    }
1835    let assignment = match statement.kind() {
1836        "assignment_expression" => statement,
1837        "expression_statement" => {
1838            single_named_child_matching(statement, |child| !child.is_extra())?
1839        }
1840        _ => return None,
1841    };
1842    if assignment.kind() != "assignment_expression" {
1843        return None;
1844    }
1845    let call = assignment.child_by_field_name("left")?;
1846    if call.kind() != "call_expression" {
1847        return None;
1848    }
1849    let function = call.child_by_field_name("function")?;
1850    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1851        return None;
1852    }
1853    let arguments = call.child_by_field_name("arguments")?;
1854    let mut actuals = argument_children(arguments);
1855    let type_node = actuals.next()?;
1856    if actuals.next().is_some()
1857        || !matches!(
1858            type_node.kind(),
1859            "identifier"
1860                | "type_identifier"
1861                | "qualified_identifier"
1862                | "scoped_type_identifier"
1863                | "template_type"
1864        )
1865    {
1866        return None;
1867    }
1868    let name_node = (0..assignment.named_child_count())
1869        .filter_map(|index| assignment.named_child(index))
1870        .filter(|child| child.kind() == "ERROR")
1871        .filter_map(|error| {
1872            (error.named_child_count() == 1)
1873                .then(|| error.named_child(0))
1874                .flatten()
1875        })
1876        .find(|node| node.kind() == "identifier")?;
1877    let name = node_text(name_node, source).trim();
1878    let type_name = node_text(type_node, source).trim();
1879    if name.is_empty() || type_name.is_empty() {
1880        return None;
1881    }
1882    Some(MacroLocalBinding {
1883        name: name.to_string(),
1884        type_name: type_name.to_string(),
1885        type_node: Some(type_node),
1886        pointer_depth: 1,
1887        proven_unit: None,
1888    })
1889}
1890
1891#[derive(Clone, PartialEq, Eq)]
1892pub struct MacroBinding {
1893    source: ProjectFile,
1894    declaration_byte: usize,
1895    definition: MacroDefinition,
1896    exact: bool,
1897}
1898
1899impl MacroBinding {
1900    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1901        Self {
1902            source: source.clone(),
1903            declaration_byte,
1904            definition: MacroDefinition::Unsupported,
1905            exact: false,
1906        }
1907    }
1908
1909    fn is_exact(&self) -> bool {
1910        self.exact
1911    }
1912
1913    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1914        Self {
1915            source: source.clone(),
1916            declaration_byte,
1917            definition: current.definition.clone(),
1918            exact: false,
1919        }
1920    }
1921}
1922
1923/// The preprocessor conditionals whose truth decides whether one macro event
1924/// applies, by the start byte of each conditional node. Empty means the event
1925/// is unconditional.
1926///
1927/// [`VisibilityIndex::macro_event_condition_value`] needs exactly the
1928/// conditional ancestors that structurally contain the event, and deciding
1929/// containment means asking [`cpp_displaced_preprocessor_boundary`] for an
1930/// `#endif` tree-sitter displaced into error recovery -- a walk of the
1931/// conditional's whole subtree. That answer is a fact about the tree alone, so
1932/// it is settled once, when the events are collected, instead of on every
1933/// replay of the event (#1496).
1934type OwningPreprocessorConditionals = Box<[usize]>;
1935
1936#[derive(Clone)]
1937pub enum MacroEvent {
1938    Define {
1939        name: String,
1940        binding: MacroBinding,
1941        byte: usize,
1942        conditionals: OwningPreprocessorConditionals,
1943    },
1944    Undef {
1945        name: String,
1946        byte: usize,
1947        conditionals: OwningPreprocessorConditionals,
1948    },
1949    Include {
1950        targets: Vec<ProjectFile>,
1951        byte: usize,
1952        conditionals: OwningPreprocessorConditionals,
1953    },
1954    Invalidate {
1955        byte: usize,
1956    },
1957}
1958
1959impl MacroEvent {
1960    pub fn byte(&self) -> usize {
1961        match self {
1962            Self::Define { byte, .. }
1963            | Self::Undef { byte, .. }
1964            | Self::Include { byte, .. }
1965            | Self::Invalidate { byte } => *byte,
1966        }
1967    }
1968}
1969
1970#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1971pub enum CallArityEvidence {
1972    Exact(usize),
1973    Unknown,
1974}
1975
1976impl CallArityEvidence {
1977    pub fn exact(self) -> Option<usize> {
1978        match self {
1979            Self::Exact(arity) => Some(arity),
1980            Self::Unknown => None,
1981        }
1982    }
1983
1984    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1985        self.exact().map(|arity| expected.accepts(arity))
1986    }
1987}
1988
1989struct CppAlias {
1990    name: String,
1991    target: String,
1992    namespace: Option<String>,
1993}
1994
1995type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1996
1997/// Why template-argument resolution failed. Definition diagnostics render
1998/// each mode differently; graph scans only care that the resolution is
1999/// unproven and match `Err(_)`.
2000#[derive(Debug, Clone, PartialEq, Eq)]
2001pub enum CppTemplateResolutionError {
2002    /// A template alias expansion revisited `alias`.
2003    AliasCycle { alias: CodeUnit },
2004    /// The explicit arguments do not bind to the declared template parameters.
2005    ArgumentBinding,
2006    /// Bound arguments do not substitute into the alias target's arguments.
2007    Substitution,
2008    /// No visible primary template declaration could be selected and
2009    /// reconciled for the specialization family.
2010    PrimarySelection,
2011    /// More than one applicable specialization remains and none is strictly
2012    /// more specialized than every other candidate.
2013    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
2014}
2015
2016/// The ambiguity candidates, deduplicated to one representative per visible
2017/// symbol so a diagnostic lists each contender once.
2018fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
2019    let mut distinct: Vec<CodeUnit> = Vec::new();
2020    for unit in units {
2021        if !distinct
2022            .iter()
2023            .any(|existing| same_visible_symbol(existing, unit))
2024        {
2025            distinct.push(unit.clone());
2026        }
2027    }
2028    distinct
2029}
2030
2031/// Enumerate macro lexical references while constructing visibility only if
2032/// the bounded source walk finds a definition or identifier call candidate.
2033pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
2034    visibility: Factory,
2035    file: &ProjectFile,
2036    root: Node<'_>,
2037    source: &str,
2038    max_references: usize,
2039    cancelled: impl FnMut() -> bool,
2040) -> MacroLexicalReferences
2041where
2042    Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
2043{
2044    crate::graph::macro_lexical::all_references(
2045        visibility,
2046        file,
2047        root,
2048        source,
2049        max_references,
2050        cancelled,
2051    )
2052}
2053
2054impl<'a> VisibilityIndex<'a> {
2055    pub fn data(&self) -> Arc<VisibilityIndexData> {
2056        Arc::clone(&self.data)
2057    }
2058
2059    pub fn from_data(
2060        cpp: &'a dyn CppSource,
2061        token: QueryToken<'a>,
2062        data: Arc<VisibilityIndexData>,
2063    ) -> Self {
2064        Self { cpp, token, data }
2065    }
2066
2067    pub fn cpp(&self) -> &'a dyn CppSource {
2068        self.cpp
2069    }
2070
2071    /// The request-scope proof this index was built with (issue #2414 step 3).
2072    pub fn token(&self) -> QueryToken<'a> {
2073        self.token
2074    }
2075
2076    /// Build the source-backed macro lexical index once per file in this
2077    /// visibility query. The entry cap keeps the query from retaining a
2078    /// workspace-sized collection when a batch visits many translation units.
2079    pub(crate) fn macro_lexical_index(
2080        &self,
2081        file: &ProjectFile,
2082        root: Node<'_>,
2083        source: &str,
2084    ) -> Arc<crate::graph::macro_lexical::MacroLexicalIndex> {
2085        let index = bounded_visibility_memo_value(
2086            &self.macro_lexical_indexes,
2087            &self.reads,
2088            file,
2089            MACRO_LEXICAL_INDEX_CACHE_CAP,
2090            || {
2091                Arc::new(crate::graph::macro_lexical::build_index(
2092                    self, file, root, source,
2093                ))
2094            },
2095        );
2096        debug_assert_eq!(
2097            index.source_len,
2098            source.len(),
2099            "macro lexical index for {file:?} was built from a different source"
2100        );
2101        index
2102    }
2103
2104    /// The complete signature rows for one callable, shared by every
2105    /// candidate fact that the visibility query asks for. C++ reconciled
2106    /// members make this read substantially more expensive than an ordinary
2107    /// metadata lookup, so keep the answer on this per-query index and cap it
2108    /// explicitly.
2109    fn callable_metadata(&self, unit: &CodeUnit) -> Arc<Vec<SignatureMetadata>> {
2110        bounded_visibility_memo_value(
2111            &self.callable_metadata,
2112            &self.reads,
2113            unit,
2114            CALLABLE_METADATA_CACHE_CAP,
2115            || Arc::new(self.cpp.stored_signature_metadata(unit)),
2116        )
2117    }
2118
2119    /// Callable linkage backed by the query-local metadata memo.
2120    pub fn callable_linkage(&self, unit: &CodeUnit) -> Option<CallableLinkage> {
2121        crate::identity::cpp_callable_linkage_from_metadata(
2122            self.callable_metadata(unit).iter().cloned(),
2123        )
2124    }
2125
2126    /// Callable arity backed by the same query-local metadata memo.
2127    pub fn callable_arity(&self, unit: &CodeUnit) -> Option<CallableArity> {
2128        brokk_bifrost_core::analyzer::model::first_callable_arity(
2129            self.callable_metadata(unit).iter().cloned(),
2130        )
2131    }
2132
2133    /// Callable parameter types backed by the same stored-metadata memo.
2134    /// Parameter spellings are source-owned facts and do not depend on the
2135    /// modifier joins that full `signature_metadata` performs for an
2136    /// out-of-line member.
2137    pub fn callable_parameter_types(&self, unit: &CodeUnit) -> Option<Vec<String>> {
2138        brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
2139            self.callable_metadata(unit).iter().cloned(),
2140        )
2141    }
2142
2143    /// Whether `file` has a structured unresolved include that is active before
2144    /// the reference at `before_byte`.
2145    ///
2146    /// Include facts are collected from the prepared tree once per visibility
2147    /// query. The reference position is still evaluated for each call because
2148    /// preprocessor visibility depends on the reference's own conditional
2149    /// context.
2150    pub fn has_unresolved_include_visible_before(
2151        &self,
2152        file: &ProjectFile,
2153        before_byte: usize,
2154    ) -> bool {
2155        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
2156            return false;
2157        };
2158        let facts = self.structured_include_facts(file, prepared.as_ref());
2159        has_unresolved_include_visible_before_in_prepared(
2160            file,
2161            prepared.as_ref(),
2162            &self.cpp.include_target_index(),
2163            &facts,
2164            before_byte,
2165            &|guards: Option<&HashSet<PreprocessorGuard>>| {
2166                self.effective_reference_guards(file, guards)
2167            },
2168        )
2169    }
2170
2171    fn structured_include_facts(
2172        &self,
2173        file: &ProjectFile,
2174        prepared: &PreparedSyntaxTree,
2175    ) -> Arc<[StructuredIncludeFact]> {
2176        visibility_memo_value(
2177            &self.structured_include_fact_cells,
2178            &self.reads,
2179            file.clone(),
2180            || collect_structured_include_facts(prepared),
2181        )
2182    }
2183
2184    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
2185    /// bypassing the include-closure walk [`Self::build`] performs.
2186    ///
2187    /// The resolver's own unit tests drive the type-resolution paths against a
2188    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
2189    /// because they need a real `CppAnalyzer`, so the struct literal they used
2190    /// to write inline is here instead of thirty-three public fields.
2191    #[cfg(any(test, feature = "test-support"))]
2192    pub fn from_visible_files_for_test(
2193        reads: ReadCapability,
2194        cpp: &'a dyn CppSource,
2195        token: QueryToken<'a>,
2196        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
2197    ) -> Self {
2198        let visible_source_files_by_root = visible_by_file
2199            .iter()
2200            .map(|(file, visible)| {
2201                (
2202                    file.clone(),
2203                    visible
2204                        .iter()
2205                        .map(|unit| unit.source().clone())
2206                        .chain(std::iter::once(file.clone()))
2207                        .collect(),
2208                )
2209            })
2210            .collect();
2211        let mut global_field_internal_linkage = HashMap::default();
2212        Self {
2213            cpp,
2214            token,
2215            data: Arc::new(VisibilityIndexData {
2216                reads,
2217                visible_by_identifier: build_visible_identifier_index(
2218                    &CppGraphSource::from_source(cpp, token),
2219                    &visible_by_file,
2220                    &visible_source_files_by_root,
2221                    &mut global_field_internal_linkage,
2222                ),
2223                global_field_internal_linkage,
2224                visible_by_file,
2225                visible_source_files_by_root,
2226                alias_cells: VisibilityMemo::default(),
2227                visible_parser_alias_name_sets: VisibilityMemo::default(),
2228                parser_alias_target_matches: VisibilityMemo::default(),
2229                ordinary_type_import_cells: VisibilityMemo::default(),
2230                project_using_index: ReplayingMemo::new(OnceLock::new()),
2231                namespace_alias_sites: VisibilityMemo::default(),
2232                callable_reference_specs: VisibilityMemo::default(),
2233                structured_include_fact_cells: VisibilityMemo::default(),
2234                include_activation_cells: VisibilityMemo::default(),
2235                compile_proven_guard_cells: VisibilityMemo::default(),
2236                include_path_admission_cells: VisibilityMemo::default(),
2237                conditional_include_projection_cells: VisibilityMemo::default(),
2238                conditional_include_projection_index_build_count: AtomicUsize::new(0),
2239                conditional_include_projection_state_count: AtomicUsize::new(0),
2240                conditional_include_target_state_count: AtomicUsize::new(0),
2241                include_activation_build_count: AtomicUsize::new(0),
2242                using_donor_activation_count: AtomicUsize::new(0),
2243                using_namespace_lookup_count: AtomicUsize::new(0),
2244                using_name_candidate_inspection_count: AtomicUsize::new(0),
2245                callable_reference_spec_build_count: AtomicUsize::new(0),
2246                alias_source_parse_counts: Mutex::new(HashMap::default()),
2247                visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2248                parser_alias_fallback_calls: AtomicUsize::new(0),
2249                parser_alias_fallback_files: AtomicUsize::new(0),
2250                parser_alias_source_parses: AtomicUsize::new(0),
2251                parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2252                field_type_facts: VisibilityMemo::default(),
2253                structured_alias_targets: VisibilityMemo::default(),
2254                callable_comparables: VisibilityMemo::default(),
2255                class_definition_agreements: VisibilityMemo::default(),
2256                callable_metadata: ReplayingMemo::new(Mutex::new(HashMap::default())),
2257                comparable_name_declarations: VisibilityMemo::default(),
2258                indexed_structural_class_scopes: VisibilityMemo::default(),
2259                indexed_enclosing_owner_scopes: VisibilityMemo::default(),
2260                precise_parent_cache: VisibilityMemo::default(),
2261                c_tag_kind_cache: VisibilityMemo::default(),
2262                c_tag_complete_definition_cache: VisibilityMemo::default(),
2263                macro_event_cells: VisibilityMemo::default(),
2264                macro_event_name_sets: VisibilityMemo::default(),
2265                macro_include_protection_cells: IncludeProtectionMemo::default(),
2266                macro_environment_checkpoints: VisibilityMemo::default(),
2267                macro_replacements: VisibilityMemo::default(),
2268                macro_local_binding_templates: VisibilityMemo::default(),
2269                macro_lexical_templates: VisibilityMemo::default(),
2270                macro_lexical_indexes: ReplayingMemo::new(Mutex::new(HashMap::default())),
2271                macro_replacement_bodies: VisibilityMemo::default(),
2272                macro_type_parameters: VisibilityMemo::default(),
2273                callable_parameter_macro_arities: VisibilityMemo::default(),
2274                macro_replacement_parse_count: AtomicUsize::new(0),
2275                macro_event_application_count: AtomicUsize::new(0),
2276                macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2277                macro_environment_copy_count: AtomicUsize::new(0),
2278                macro_environment_request_count: AtomicUsize::new(0),
2279                cpp_template_metadata: HashMap::default(),
2280                cpp_template_families: HashMap::default(),
2281                qualified_candidate_inspections: AtomicUsize::new(0),
2282                target_preserving_type_resolution_count: AtomicUsize::new(0),
2283                visibility_identifier_lookup_count: 0,
2284                visibility_identifier_batch_count: 0,
2285            }),
2286        }
2287    }
2288
2289    /// The index's own C++ source, in the dispatching-analyzer shape.
2290    ///
2291    /// Four resolution paths reach the workspace through the C++ analyzer they
2292    /// already hold rather than through the analyzer the query was issued
2293    /// against; before the move they passed `&CppAnalyzer` straight into a
2294    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
2295    fn cpp_source(&self) -> CppGraphSource<'a> {
2296        CppGraphSource::from_source(self.cpp, self.token)
2297    }
2298
2299    pub fn build(
2300        reads: ReadCapability,
2301        cpp: &'a dyn CppSource,
2302        token: QueryToken<'a>,
2303        analyzer: &CppGraphSource<'_>,
2304        roots: &HashSet<ProjectFile>,
2305    ) -> Self {
2306        Self::build_with_cancellation(reads, cpp, token, analyzer, roots, None)
2307    }
2308
2309    pub fn build_with_cancellation(
2310        reads: ReadCapability,
2311        cpp: &'a dyn CppSource,
2312        token: QueryToken<'a>,
2313        analyzer: &CppGraphSource<'_>,
2314        roots: &HashSet<ProjectFile>,
2315        cancellation: Option<&CancellationToken>,
2316    ) -> Self {
2317        let visibility_started = Instant::now();
2318        let include_targets = cpp.include_target_index();
2319        let includes_started = Instant::now();
2320        let mut include_graph = IncludeGraph::default();
2321        for root in roots {
2322            include_graph.extend_with(root, cancellation, &mut |file| {
2323                cpp.canonical_include_paths(token, file)
2324                    .unwrap_or_default()
2325                    .into_iter()
2326                    .flat_map(|include| {
2327                        resolve_include_targets_with_index(file, &include, &include_targets)
2328                    })
2329                    .collect()
2330            });
2331        }
2332        let include_elapsed = includes_started.elapsed();
2333        let include_file_count = include_graph.files().count();
2334        let visible_source_files_by_root = roots
2335            .iter()
2336            .map(|root| {
2337                (
2338                    root.clone(),
2339                    include_graph.reachable_files(root, cancellation),
2340                )
2341            })
2342            .collect::<HashMap<_, _>>();
2343        let mut visibility_stats = BoundedVisibilityStats::default();
2344        let mut visible_by_file = build_bounded_visible_declarations(
2345            cpp,
2346            token,
2347            analyzer,
2348            roots,
2349            &visible_source_files_by_root,
2350            cancellation,
2351            &mut visibility_stats,
2352        );
2353        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2354            eprintln!(
2355                "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={}",
2356                visibility_started.elapsed().as_millis(),
2357                include_elapsed.as_millis(),
2358                include_file_count,
2359                visibility_stats.rounds,
2360                visibility_stats.root_names,
2361                visibility_stats.identifier_lookups,
2362                visibility_stats.identifier_batches,
2363                visibility_stats.candidate_units,
2364                visibility_stats.candidate_sources,
2365                visibility_stats.declaration_reads,
2366                visibility_stats.declaration_units,
2367                visibility_stats.selected_units,
2368                visibility_stats.dependency_source_names,
2369                visibility_stats.dependency_names,
2370                visibility_stats.lookup_elapsed.as_millis(),
2371                visibility_stats.declaration_elapsed.as_millis(),
2372                visibility_stats.dependency_source_elapsed.as_millis(),
2373            );
2374        }
2375        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2376        let finalize_started = Instant::now();
2377        if report_stats {
2378            eprintln!(
2379                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2380                visible_by_file.len(),
2381                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2382            );
2383        }
2384        let owner_started = Instant::now();
2385        if report_stats {
2386            eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2387        }
2388        let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2389        if report_stats {
2390            eprintln!(
2391                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2392                owner_stats.unseen_owners,
2393                owner_stats.definition_lookups,
2394                owner_stats.admitted,
2395                owner_started.elapsed().as_millis(),
2396            );
2397        }
2398        let mut global_field_internal_linkage = HashMap::default();
2399        let identifier_started = Instant::now();
2400        if report_stats {
2401            eprintln!(
2402                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2403            );
2404        }
2405        let visible_by_identifier = build_visible_identifier_index(
2406            analyzer,
2407            &visible_by_file,
2408            &visible_source_files_by_root,
2409            &mut global_field_internal_linkage,
2410        );
2411        if report_stats {
2412            eprintln!(
2413                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2414                visible_by_identifier.len(),
2415                visible_by_identifier
2416                    .values()
2417                    .map(HashMap::len)
2418                    .sum::<usize>(),
2419                visible_by_identifier
2420                    .values()
2421                    .flat_map(HashMap::values)
2422                    .map(Vec::len)
2423                    .sum::<usize>(),
2424                identifier_started.elapsed().as_millis(),
2425            );
2426        }
2427        let mut cpp_template_metadata = HashMap::default();
2428        let metadata_started = Instant::now();
2429        let mut template_classes = 0usize;
2430        if report_stats {
2431            eprintln!(
2432                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2433            );
2434        }
2435        for unit in visible_by_file
2436            .values()
2437            .flatten()
2438            .filter(|unit| unit.is_class())
2439        {
2440            template_classes += 1;
2441            if cpp_template_metadata.contains_key(unit) {
2442                continue;
2443            }
2444            if let Some(metadata) = cpp.template_metadata(unit) {
2445                cpp_template_metadata.insert(unit.clone(), metadata);
2446            }
2447        }
2448        if report_stats {
2449            eprintln!(
2450                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2451                template_classes,
2452                cpp_template_metadata.len(),
2453                metadata_started.elapsed().as_millis(),
2454            );
2455        }
2456        let families_started = Instant::now();
2457        if report_stats {
2458            eprintln!(
2459                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2460            );
2461        }
2462        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2463        for (unit, metadata) in &cpp_template_metadata {
2464            cpp_template_families
2465                .entry(metadata.primary_fq_name.clone())
2466                .or_default()
2467                .push(unit.clone());
2468        }
2469        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
2470        // order above is a function of those hashes. Two mirrored headers can
2471        // declare one specialization; `select_template_specialization` treats
2472        // them as interchangeable and returns the family's first entry, so an
2473        // unsorted family made the reported declaration depend on the
2474        // workspace's absolute path and on unrelated files (#1836). Order the
2475        // family exactly as `build_visible_identifier_index` orders its
2476        // per-identifier candidate lists.
2477        for family in cpp_template_families.values_mut() {
2478            sort_lookup_units(family);
2479        }
2480        if report_stats {
2481            eprintln!(
2482                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2483                cpp_template_families.len(),
2484                cpp_template_families.values().map(Vec::len).sum::<usize>(),
2485                families_started.elapsed().as_millis(),
2486            );
2487            eprintln!(
2488                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2489                visible_by_file.len(),
2490                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2491                finalize_started.elapsed().as_millis(),
2492                visibility_started.elapsed().as_millis(),
2493            );
2494        }
2495        Self {
2496            cpp,
2497            token,
2498            data: Arc::new(VisibilityIndexData {
2499                reads,
2500                visible_by_file,
2501                visible_by_identifier,
2502                global_field_internal_linkage,
2503                visible_source_files_by_root,
2504                alias_cells: VisibilityMemo::default(),
2505                visible_parser_alias_name_sets: VisibilityMemo::default(),
2506                parser_alias_target_matches: VisibilityMemo::default(),
2507                ordinary_type_import_cells: VisibilityMemo::default(),
2508                project_using_index: ReplayingMemo::new(OnceLock::new()),
2509                namespace_alias_sites: VisibilityMemo::default(),
2510                callable_reference_specs: VisibilityMemo::default(),
2511                structured_include_fact_cells: VisibilityMemo::default(),
2512                include_activation_cells: VisibilityMemo::default(),
2513                compile_proven_guard_cells: VisibilityMemo::default(),
2514                include_path_admission_cells: VisibilityMemo::default(),
2515                conditional_include_projection_cells: VisibilityMemo::default(),
2516                #[cfg(any(test, feature = "test-support"))]
2517                conditional_include_projection_index_build_count: AtomicUsize::new(0),
2518                #[cfg(any(test, feature = "test-support"))]
2519                conditional_include_projection_state_count: AtomicUsize::new(0),
2520                #[cfg(any(test, feature = "test-support"))]
2521                conditional_include_target_state_count: AtomicUsize::new(0),
2522                #[cfg(any(test, feature = "test-support"))]
2523                include_activation_build_count: AtomicUsize::new(0),
2524                #[cfg(any(test, feature = "test-support"))]
2525                using_donor_activation_count: AtomicUsize::new(0),
2526                #[cfg(any(test, feature = "test-support"))]
2527                using_namespace_lookup_count: AtomicUsize::new(0),
2528                #[cfg(any(test, feature = "test-support"))]
2529                using_name_candidate_inspection_count: AtomicUsize::new(0),
2530                #[cfg(any(test, feature = "test-support"))]
2531                callable_reference_spec_build_count: AtomicUsize::new(0),
2532                #[cfg(any(test, feature = "test-support"))]
2533                alias_source_parse_counts: Mutex::new(HashMap::default()),
2534                #[cfg(any(test, feature = "test-support"))]
2535                visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2536                parser_alias_fallback_calls: AtomicUsize::new(0),
2537                parser_alias_fallback_files: AtomicUsize::new(0),
2538                parser_alias_source_parses: AtomicUsize::new(0),
2539                parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2540                field_type_facts: VisibilityMemo::default(),
2541                structured_alias_targets: VisibilityMemo::default(),
2542                callable_comparables: VisibilityMemo::default(),
2543                class_definition_agreements: VisibilityMemo::default(),
2544                callable_metadata: ReplayingMemo::new(Mutex::new(HashMap::default())),
2545                comparable_name_declarations: VisibilityMemo::default(),
2546                indexed_structural_class_scopes: VisibilityMemo::default(),
2547                indexed_enclosing_owner_scopes: VisibilityMemo::default(),
2548                precise_parent_cache: VisibilityMemo::default(),
2549                c_tag_kind_cache: VisibilityMemo::default(),
2550                c_tag_complete_definition_cache: VisibilityMemo::default(),
2551                macro_event_cells: VisibilityMemo::default(),
2552                macro_event_name_sets: VisibilityMemo::default(),
2553                macro_include_protection_cells: IncludeProtectionMemo::default(),
2554                macro_environment_checkpoints: VisibilityMemo::default(),
2555                macro_replacements: VisibilityMemo::default(),
2556                macro_local_binding_templates: VisibilityMemo::default(),
2557                macro_lexical_templates: VisibilityMemo::default(),
2558                macro_lexical_indexes: ReplayingMemo::new(Mutex::new(HashMap::default())),
2559                macro_replacement_bodies: VisibilityMemo::default(),
2560                macro_type_parameters: VisibilityMemo::default(),
2561                callable_parameter_macro_arities: VisibilityMemo::default(),
2562                #[cfg(any(test, feature = "test-support"))]
2563                macro_replacement_parse_count: AtomicUsize::new(0),
2564                #[cfg(any(test, feature = "test-support"))]
2565                macro_event_application_count: AtomicUsize::new(0),
2566                #[cfg(any(test, feature = "test-support"))]
2567                macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2568                #[cfg(any(test, feature = "test-support"))]
2569                macro_environment_copy_count: AtomicUsize::new(0),
2570                #[cfg(any(test, feature = "test-support"))]
2571                macro_environment_request_count: AtomicUsize::new(0),
2572                cpp_template_metadata,
2573                cpp_template_families,
2574                #[cfg(any(test, feature = "test-support"))]
2575                qualified_candidate_inspections: AtomicUsize::new(0),
2576                #[cfg(any(test, feature = "test-support"))]
2577                target_preserving_type_resolution_count: AtomicUsize::new(0),
2578                #[cfg(any(test, feature = "test-support"))]
2579                visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2580                #[cfg(any(test, feature = "test-support"))]
2581                visibility_identifier_batch_count: visibility_stats.identifier_batches,
2582            }),
2583        }
2584    }
2585
2586    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2587        if file == target.source() {
2588            return true;
2589        }
2590        if self.global_field_has_internal_linkage(target) {
2591            return self
2592                .visible_source_files_by_root
2593                .get(file)
2594                .is_some_and(|sources| sources.contains(target.source()));
2595        }
2596        self.visible_by_file
2597            .get(file)
2598            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2599    }
2600
2601    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2602        self.global_field_internal_linkage
2603            .get(unit)
2604            .copied()
2605            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2606    }
2607
2608    pub fn call_arity_evidence(
2609        &self,
2610        file: &ProjectFile,
2611        call: Node<'_>,
2612        source: &str,
2613    ) -> CallArityEvidence {
2614        self.call_arity_evidence_at(file, call, source, call.start_byte())
2615    }
2616
2617    /// Argument-count evidence for a call whose macro environment is not the
2618    /// one at its own byte offset.
2619    ///
2620    /// A call recovered from a macro replacement lives in a sentinel parse of
2621    /// its own, so its node offsets say nothing about which macros are active.
2622    /// `environment_byte` names the position in `file` whose macro environment
2623    /// governs the call: the macro definition site for a replacement body.
2624    pub fn call_arity_evidence_at(
2625        &self,
2626        file: &ProjectFile,
2627        call: Node<'_>,
2628        source: &str,
2629        environment_byte: usize,
2630    ) -> CallArityEvidence {
2631        let Some(arguments) = call
2632            .child_by_field_name("arguments")
2633            .or_else(|| call.child_by_field_name("parameters"))
2634            .or_else(|| call.child_by_field_name("value"))
2635            .or_else(|| first_named_child_of_kind(call, "argument_list"))
2636            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2637        else {
2638            return CallArityEvidence::Exact(0);
2639        };
2640        let recovered_c_keyword_arguments =
2641            recovered_c_keyword_argument_count(file, call, arguments, source);
2642        let c_semantics = reference_uses_c_semantics(self.cpp, file);
2643        let arguments = argument_children(arguments)
2644            .flat_map(|argument| {
2645                recovered_c_new_expression_arguments(argument, c_semantics)
2646                    .map(Vec::from)
2647                    .unwrap_or_else(|| vec![argument])
2648            })
2649            .collect::<Vec<_>>();
2650        if arguments
2651            .iter()
2652            .all(|argument| !argument_shape_may_change_arity(*argument))
2653        {
2654            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2655        }
2656        let environment = self.macro_environment(file, environment_byte);
2657        let mut stack = Vec::new();
2658        let mut total = recovered_c_keyword_arguments;
2659        for argument in arguments {
2660            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2661                return CallArityEvidence::Unknown;
2662            }
2663            let CallArityEvidence::Exact(spread) =
2664                self.argument_arity_evidence(argument, source, &environment, &mut stack)
2665            else {
2666                return CallArityEvidence::Unknown;
2667            };
2668            total += spread;
2669        }
2670        CallArityEvidence::Exact(total)
2671    }
2672
2673    fn argument_arity_evidence(
2674        &self,
2675        argument: Node<'_>,
2676        source: &str,
2677        environment: &MacroEnvironment,
2678        stack: &mut Vec<(ProjectFile, usize)>,
2679    ) -> CallArityEvidence {
2680        let (name, invocation_arguments, function_like) = match argument.kind() {
2681            "identifier" => (node_text(argument, source), None, false),
2682            "call_expression" => {
2683                let Some(function) = argument.child_by_field_name("function") else {
2684                    return CallArityEvidence::Exact(1);
2685                };
2686                if function.kind() != "identifier" {
2687                    return CallArityEvidence::Exact(1);
2688                }
2689                let Some(arguments) = argument.child_by_field_name("arguments") else {
2690                    return CallArityEvidence::Exact(1);
2691                };
2692                (node_text(function, source), Some(arguments), true)
2693            }
2694            _ => return CallArityEvidence::Exact(1),
2695        };
2696        let Some(binding) = environment.binding(name) else {
2697            return if environment.unknown_names {
2698                CallArityEvidence::Unknown
2699            } else {
2700                CallArityEvidence::Exact(1)
2701            };
2702        };
2703        if !binding.is_exact() {
2704            return CallArityEvidence::Unknown;
2705        }
2706        match (&binding.definition, invocation_arguments, function_like) {
2707            (MacroDefinition::Object { replacement }, None, false) => self
2708                .replacement_arity_evidence(
2709                    replacement,
2710                    &[],
2711                    &[],
2712                    source,
2713                    environment,
2714                    stack,
2715                    binding,
2716                ),
2717            (
2718                MacroDefinition::Function {
2719                    parameters,
2720                    replacement,
2721                },
2722                Some(arguments),
2723                true,
2724            ) => {
2725                let actuals = argument_children(arguments).collect::<Vec<_>>();
2726                if actuals.len() != parameters.len() {
2727                    CallArityEvidence::Unknown
2728                } else {
2729                    self.replacement_arity_evidence(
2730                        replacement,
2731                        parameters,
2732                        &actuals,
2733                        source,
2734                        environment,
2735                        stack,
2736                        binding,
2737                    )
2738                }
2739            }
2740            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2741            _ => CallArityEvidence::Unknown,
2742        }
2743    }
2744
2745    #[allow(clippy::too_many_arguments)]
2746    fn replacement_arity_evidence(
2747        &self,
2748        replacement: &str,
2749        parameters: &[String],
2750        actuals: &[Node<'_>],
2751        actual_source: &str,
2752        environment: &MacroEnvironment,
2753        stack: &mut Vec<(ProjectFile, usize)>,
2754        binding: &MacroBinding,
2755    ) -> CallArityEvidence {
2756        let identity = (binding.source.clone(), binding.declaration_byte);
2757        if stack.contains(&identity) || replacement.trim().is_empty() {
2758            return CallArityEvidence::Unknown;
2759        }
2760        stack.push(identity);
2761        let parsed = self.parsed_macro_replacement(binding, replacement);
2762        let evidence = (|| {
2763            let ParsedMacroReplacement::Parsed {
2764                source: sentinel,
2765                tree,
2766            } = parsed.as_ref()
2767            else {
2768                return None;
2769            };
2770            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2771            let arguments = call.child_by_field_name("arguments")?;
2772            let mut total = 0usize;
2773            for argument in argument_children(arguments) {
2774                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2775                    return None;
2776                }
2777                if argument.kind() == "identifier"
2778                    && let Some(parameter_index) = parameters
2779                        .iter()
2780                        .position(|parameter| parameter == node_text(argument, sentinel))
2781                {
2782                    if !macro_expansion_shape_is_safe(
2783                        actuals[parameter_index],
2784                        actual_source,
2785                        &[],
2786                        environment,
2787                    ) {
2788                        return None;
2789                    }
2790                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2791                        actuals[parameter_index],
2792                        actual_source,
2793                        environment,
2794                        stack,
2795                    ) else {
2796                        return None;
2797                    };
2798                    total += spread;
2799                    continue;
2800                }
2801                let CallArityEvidence::Exact(spread) =
2802                    self.argument_arity_evidence(argument, sentinel, environment, stack)
2803                else {
2804                    return None;
2805                };
2806                total += spread;
2807            }
2808            Some(CallArityEvidence::Exact(total))
2809        })()
2810        .unwrap_or(CallArityEvidence::Unknown);
2811        stack.pop();
2812        evidence
2813    }
2814
2815    fn parsed_macro_replacement(
2816        &self,
2817        binding: &MacroBinding,
2818        replacement: &str,
2819    ) -> Arc<ParsedMacroReplacement> {
2820        visibility_memo_value(
2821            &self.macro_replacements,
2822            &self.reads,
2823            (binding.source.clone(), binding.declaration_byte),
2824            || {
2825                #[cfg(any(test, feature = "test-support"))]
2826                self.macro_replacement_parse_count
2827                    .fetch_add(1, Ordering::Relaxed);
2828                let parsed = parse_sentinel(
2829                    "void __bifrost_macro_arity() { __bifrost_macro_call(",
2830                    SentinelInput::replacement(replacement),
2831                    "); }",
2832                );
2833                let parsed = if parsed.tree.root_node().has_error() {
2834                    ParsedMacroReplacement::Unsupported
2835                } else {
2836                    ParsedMacroReplacement::Parsed {
2837                        source: parsed.source,
2838                        tree: parsed.tree,
2839                    }
2840                };
2841                Arc::new(parsed)
2842            },
2843        )
2844    }
2845
2846    /// Recover a typed local declared by an active C function-like macro.
2847    ///
2848    /// This is intentionally narrower than macro expansion. The replacement
2849    /// must parse as one declaration, and the invocation must bind every
2850    /// formal parameter to one structured argument. That is sufficient for
2851    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
2852    /// can make the binding provisional without erasing its last known
2853    /// definition; an explicit conflicting definition still replaces it with
2854    /// Unsupported. Malformed and statement-producing macros also fail closed.
2855    pub fn function_macro_local_binding<'tree>(
2856        &self,
2857        file: &ProjectFile,
2858        statement: Node<'tree>,
2859        source: &str,
2860    ) -> Option<MacroLocalBinding<'tree>> {
2861        if !is_c_source_file(file) {
2862            return None;
2863        }
2864        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2865            return Some(binding);
2866        }
2867        let call = match statement.kind() {
2868            "expression_statement" => {
2869                single_named_child_matching(statement, |child| !child.is_extra())?
2870            }
2871            _ => statement,
2872        };
2873        let (function, actuals) = function_macro_invocation_parts(call)?;
2874        let environment = self.macro_environment(file, call.start_byte());
2875        let function_name = node_text(function, source);
2876        let binding = environment.binding(function_name)?;
2877        let MacroDefinition::Function {
2878            parameters,
2879            replacement,
2880        } = &binding.definition
2881        else {
2882            return None;
2883        };
2884        if actuals.len() != parameters.len() {
2885            return None;
2886        }
2887        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2888        let (type_name, type_node) = match &template.declared_type {
2889            MacroLocalBindingTypeTemplate::Parameter(index) => {
2890                let actual = *actuals.get(*index)?;
2891                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2892                    return None;
2893                }
2894                let type_node = macro_type_argument_node(actual, source).unwrap_or(actual);
2895                (
2896                    node_text(actual, source).trim().to_string(),
2897                    Some(type_node),
2898                )
2899            }
2900            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2901        };
2902        if type_name.is_empty() {
2903            return None;
2904        }
2905        Some(MacroLocalBinding {
2906            name: template.name.clone(),
2907            type_name,
2908            type_node,
2909            pointer_depth: template.pointer_depth,
2910            proven_unit: None,
2911        })
2912    }
2913
2914    /// Recover the typed receiver established by a C container macro
2915    /// assignment, such as `value = container_of(ptr, struct item, link)`.
2916    ///
2917    /// The macro definition's parsed replacement identifies the one formal
2918    /// parameter used in type position.  The invocation supplies the actual
2919    /// type node, which is then resolved through the ordinary visibility
2920    /// index.  Calls with no unique type-position parameter, an unresolved
2921    /// type, or an uncertain macro environment remain unproven.
2922    pub fn function_macro_container_binding<'tree>(
2923        &self,
2924        analyzer: &CppGraphSource<'_>,
2925        file: &ProjectFile,
2926        assignment: Node<'tree>,
2927        source: &str,
2928    ) -> Option<MacroLocalBinding<'tree>> {
2929        if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2930            return None;
2931        }
2932        let name_node = assignment.child_by_field_name("left")?;
2933        if name_node.kind() != "identifier" {
2934            return None;
2935        }
2936        let call = assignment.child_by_field_name("right")?;
2937        if call.kind() != "call_expression" {
2938            return None;
2939        }
2940        let function = call.child_by_field_name("function")?;
2941        if function.kind() != "identifier" {
2942            return None;
2943        }
2944        let arguments = call.child_by_field_name("arguments")?;
2945        let actuals = argument_children(arguments).collect::<Vec<_>>();
2946        let function_name = node_text(function, source);
2947        let environment = self.macro_environment(file, call.start_byte());
2948        let binding = environment.binding(function_name)?;
2949        if !binding.is_exact() {
2950            return None;
2951        }
2952        let MacroDefinition::Function {
2953            parameters,
2954            replacement,
2955        } = &binding.definition
2956        else {
2957            return None;
2958        };
2959        if actuals.len() != parameters.len() {
2960            return None;
2961        }
2962        let body = self.parsed_macro_replacement_body(
2963            &(binding.source.clone(), binding.declaration_byte),
2964            parameters,
2965            replacement,
2966        )?;
2967        let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2968        let type_argument = *actuals.get(type_parameter)?;
2969        let type_node = macro_type_argument_node(type_argument, source)?;
2970        let type_name = node_text(type_node, source).trim().to_string();
2971        if type_name.is_empty() {
2972            return None;
2973        }
2974        let explicit_tag = match node_text(type_argument, source) {
2975            "struct" => Some(CppCTagKind::Struct),
2976            "union" => Some(CppCTagKind::Union),
2977            _ => None,
2978        };
2979        let resolved_type = if let Some(tag) = explicit_tag {
2980            let candidates = self
2981                .visible_identifier_candidates(file, &type_name)
2982                .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2983                .collect::<Vec<_>>();
2984            self.resolve_type_candidates(
2985                analyzer,
2986                file,
2987                &candidates,
2988                TypeCandidateResolution::Canonical,
2989            )
2990            .ok()
2991        } else {
2992            self.resolve_type_node_result(file, type_node, source)
2993                .ok()
2994                .flatten()
2995        };
2996        let proven_unit = resolved_type?;
2997        Some(MacroLocalBinding {
2998            name: node_text(name_node, source).to_string(),
2999            type_name,
3000            type_node: Some(type_node),
3001            pointer_depth: 1,
3002            proven_unit: Some(proven_unit),
3003        })
3004    }
3005
3006    /// Recover the invocation-specific type of a replacement local visible
3007    /// at the selected source token.
3008    pub fn macro_local_binding_at<'tree>(
3009        &self,
3010        file: &ProjectFile,
3011        root: Node<'tree>,
3012        source: &str,
3013        start_byte: usize,
3014        end_byte: usize,
3015    ) -> Option<MacroLocalBinding<'tree>> {
3016        crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
3017    }
3018
3019    /// Resolve the source-backed lexical macro declaration for one source
3020    /// range. The range may be a point range (`start..start + 1`) inside an
3021    /// identifier, which is the form used by goto-definition queries.
3022    pub fn macro_lexical_binding(
3023        &self,
3024        file: &ProjectFile,
3025        root: Node<'_>,
3026        source: &str,
3027        start_byte: usize,
3028        end_byte: usize,
3029    ) -> Option<MacroLexicalBinding> {
3030        crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
3031    }
3032
3033    /// Enumerate source ranges whose lexical meaning comes from a
3034    /// function-like macro formal or replacement local. Definition spellings
3035    /// themselves are omitted; callers needing goto-definition on a spelling
3036    /// use [`Self::macro_lexical_binding`].
3037    pub fn macro_lexical_references(
3038        &self,
3039        file: &ProjectFile,
3040        root: Node<'_>,
3041        source: &str,
3042        max_references: usize,
3043        cancelled: impl FnMut() -> bool,
3044    ) -> MacroLexicalReferences {
3045        crate::graph::macro_lexical::all_references(
3046            || self,
3047            file,
3048            root,
3049            source,
3050            max_references,
3051            cancelled,
3052        )
3053    }
3054
3055    /// Return the source identity of the exact active function-like macro
3056    /// binding. The definition AST is hydrated by the lexical helper from
3057    /// this `(ProjectFile, declaration byte)` pair, so no parser node escapes
3058    /// the prepared source borrow.
3059    pub(crate) fn function_macro_binding_at(
3060        &self,
3061        file: &ProjectFile,
3062        name: &str,
3063        before_byte: usize,
3064    ) -> Option<(ProjectFile, usize)> {
3065        let environment = self.macro_environment(file, before_byte);
3066        let binding = environment.binding(name)?;
3067        if binding.is_exact()
3068            && matches!(
3069                binding.definition,
3070                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
3071            )
3072        {
3073            return Some((binding.source.clone(), binding.declaration_byte));
3074        }
3075        // A definition under an unknown condition is still certain at a use
3076        // within that same branch. The checkpoint retains the event that made
3077        // the binding uncertain. Require that exact defining event, so an
3078        // intervening undef, include, or conditional redefinition cannot be
3079        // bypassed by a lexical search for an older macro.
3080        if binding.source != *file {
3081            return None;
3082        }
3083        let prepared = self.cpp.prepared_syntax(self.token, file)?;
3084        let root = prepared.tree().root_node();
3085        let source = prepared.source();
3086        let reference = root.descendant_for_byte_range(
3087            before_byte,
3088            before_byte.saturating_add(1).min(source.len()),
3089        )?;
3090        let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
3091        let cell = self.macro_event_cell(file);
3092        let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
3093        let event = events
3094            .iter()
3095            .find(|event| event.byte() == binding.declaration_byte)?;
3096        let MacroEvent::Define {
3097            name: defined_name,
3098            binding: definition,
3099            conditionals,
3100            ..
3101        } = event
3102        else {
3103            return None;
3104        };
3105        if defined_name != name
3106            || conditionals.is_empty()
3107            || !conditionals
3108                .iter()
3109                .all(|condition| reference_conditions.contains(condition))
3110            || !matches!(
3111                definition.definition,
3112                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
3113            )
3114        {
3115            return None;
3116        }
3117        Some((definition.source.clone(), definition.declaration_byte))
3118    }
3119
3120    /// Return the type argument at a C function-like macro invocation.
3121    ///
3122    /// Exact local macro definitions identify type formals from their parsed
3123    /// replacement. An unavailable definition is deliberately not inferred:
3124    /// C has no AST distinction between an external macro call and an
3125    /// ordinary call to an unindexed function.
3126    pub fn function_macro_type_argument<'tree>(
3127        &self,
3128        file: &ProjectFile,
3129        node: Node<'tree>,
3130        source: &str,
3131    ) -> Option<Node<'tree>> {
3132        if !is_c_source_file(file) {
3133            return None;
3134        }
3135        let (call, argument_index, argument) = c_function_macro_argument(node)?;
3136        let function = call.child_by_field_name("function")?;
3137        let function_name = node_text(function, source);
3138        if function_name.is_empty() {
3139            return None;
3140        }
3141        if !self.file_defines_macro_name(file, function_name)
3142            && !self
3143                .visible_identifier_candidates(file, function_name)
3144                .any(|candidate| candidate.is_macro())
3145        {
3146            return None;
3147        }
3148        let environment = self.macro_environment(file, call.start_byte());
3149        let binding = environment.binding(function_name)?;
3150        if !binding.is_exact() {
3151            return None;
3152        }
3153        let (parameters, replacement, variadic) = match &binding.definition {
3154            MacroDefinition::Function {
3155                parameters,
3156                replacement,
3157            } => (parameters, replacement, false),
3158            MacroDefinition::VariadicFunction {
3159                parameters,
3160                replacement,
3161            } => (parameters, replacement, true),
3162            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3163        };
3164        let actuals = call
3165            .child_by_field_name("arguments")
3166            .map(argument_children)
3167            .into_iter()
3168            .flatten()
3169            .collect::<Vec<_>>();
3170        let arity_matches = if variadic {
3171            actuals.len() >= parameters.len()
3172        } else {
3173            actuals.len() == parameters.len()
3174        };
3175        let type_parameters = self.macro_type_parameter_indices(
3176            &(binding.source.clone(), binding.declaration_byte),
3177            parameters,
3178            replacement,
3179        )?;
3180        if !arity_matches || !type_parameters.contains(&argument_index) {
3181            return None;
3182        }
3183        let type_node = macro_type_argument_node(argument, source)?;
3184        if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
3185            return None;
3186        }
3187        Some(type_node)
3188    }
3189
3190    fn macro_local_binding_template(
3191        &self,
3192        binding: &MacroBinding,
3193        parameters: &[String],
3194        replacement: &str,
3195    ) -> Option<Arc<MacroLocalBindingTemplate>> {
3196        visibility_memo_value(
3197            &self.macro_local_binding_templates,
3198            &self.reads,
3199            (binding.source.clone(), binding.declaration_byte),
3200            || {
3201                let key = (binding.source.clone(), binding.declaration_byte);
3202                let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
3203                let sentinel = body.source.as_str();
3204                let statements = body.statements()?;
3205                let declaration =
3206                    single_named_child_matching(statements, |child| !child.is_extra())?;
3207                if declaration.kind() != "declaration" {
3208                    return None;
3209                }
3210                let type_node = declaration
3211                    .child_by_field_name("type")
3212                    .or_else(|| first_type_child(declaration))?;
3213                let declarator = declaration.child_by_field_name("declarator").or_else(|| {
3214                    let mut cursor = declaration.walk();
3215                    declaration.named_children(&mut cursor).find_map(|child| {
3216                        if child.kind() == "init_declarator" {
3217                            child.child_by_field_name("declarator")
3218                        } else {
3219                            is_declarator_node(child).then_some(child)
3220                        }
3221                    })
3222                })?;
3223                let name = extract_variable_name(declarator, sentinel)?;
3224                let pointer_depth =
3225                    declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
3226                let type_text = node_text(type_node, sentinel).trim();
3227                let declared_type = parameters
3228                    .iter()
3229                    .position(|parameter| parameter == type_text)
3230                    .map(MacroLocalBindingTypeTemplate::Parameter)
3231                    .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
3232                Some(Arc::new(MacroLocalBindingTemplate {
3233                    name,
3234                    declared_type,
3235                    pointer_depth,
3236                }))
3237            },
3238        )
3239    }
3240
3241    /// The parsed replacement body of the function-like macro `definition`
3242    /// defines, or `None` when the replacement cannot be recovered exactly.
3243    ///
3244    /// `definition` is the defining `preproc_function_def` node in `file`, so
3245    /// the result describes that definition rather than whichever same-named
3246    /// macro a later reference resolves to.
3247    pub fn function_macro_replacement_body(
3248        &self,
3249        file: &ProjectFile,
3250        definition: Node<'_>,
3251        source: &str,
3252    ) -> Option<Arc<ParsedReplacementBody>> {
3253        debug_assert_eq!(definition.kind(), "preproc_function_def");
3254        let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
3255            MacroDefinition::Function {
3256                parameters,
3257                replacement,
3258            }
3259            | MacroDefinition::VariadicFunction {
3260                parameters,
3261                replacement,
3262            } => (parameters, replacement),
3263            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3264        };
3265        self.parsed_macro_replacement_body(
3266            &(file.clone(), definition.start_byte()),
3267            &parameters,
3268            &replacement,
3269        )
3270    }
3271
3272    /// Parse one function-like macro replacement inside the shared sentinel.
3273    ///
3274    /// The parse fails closed, and the failure is cached, whenever the
3275    /// sentinel tree carries an error or the replacement uses preprocessor
3276    /// syntax that has no C++ meaning. Token pasting and stringizing produce
3277    /// `ERROR` nodes; `__VA_ARGS__` parses as an ordinary identifier and is
3278    /// therefore rejected from the parsed tree instead of the source text.
3279    fn parsed_macro_replacement_body(
3280        &self,
3281        key: &(ProjectFile, usize),
3282        parameters: &[String],
3283        replacement: &str,
3284    ) -> Option<Arc<ParsedReplacementBody>> {
3285        visibility_memo_value(
3286            &self.macro_replacement_bodies,
3287            &self.reads,
3288            key.clone(),
3289            || {
3290                if replacement.trim().is_empty() {
3291                    return None;
3292                }
3293                let (source, tree, original_offsets) =
3294                    Self::parse_macro_replacement_body(replacement, parameters)?;
3295                let body = ParsedReplacementBody {
3296                    source,
3297                    tree,
3298                    body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3299                    parameters: parameters.to_vec(),
3300                    original_offsets,
3301                };
3302                body.statements()?;
3303                if body.expands_variadic_arguments() {
3304                    return None;
3305                }
3306                Some(Arc::new(body))
3307            },
3308        )
3309    }
3310
3311    /// Parse a replacement while preserving a map from the parse buffer back
3312    /// to the original logical-line bytes. A block formal can occur where C
3313    /// requires a statement terminator (`if (condition) block`); the caller's
3314    /// actual block supplies that terminator only after macro substitution.
3315    /// Tree-sitter exposes the formal as an identifier inside recovery, so a
3316    /// semicolon is inserted at that AST-derived boundary for the fallback
3317    /// parse. No identifier text is scanned to find the insertion point.
3318    fn parse_macro_replacement_body(
3319        replacement: &str,
3320        parameters: &[String],
3321    ) -> Option<(String, Tree, MacroOriginMap)> {
3322        let parsed = parse_sentinel(
3323            MACRO_BODY_SENTINEL_PREFIX,
3324            SentinelInput::replacement(replacement),
3325            MACRO_BODY_SENTINEL_SUFFIX,
3326        );
3327        let normalized_end = parsed.source.len() - MACRO_BODY_SENTINEL_SUFFIX.len() - 1;
3328        let normalized = &parsed.source[MACRO_BODY_SENTINEL_PREFIX.len()..normalized_end];
3329        let normalized_offsets = &parsed.original_offsets;
3330        let source = parsed.source.as_str();
3331        let tree = &parsed.tree;
3332        if !tree.root_node().has_error() {
3333            return Some((
3334                parsed.source,
3335                parsed.tree,
3336                parsed.original_offsets.into_boxed_slice(),
3337            ));
3338        }
3339
3340        let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3341        let mut insertion_points = Vec::new();
3342        let mut stack = vec![tree.root_node()];
3343        while let Some(node) = stack.pop() {
3344            if matches!(
3345                node.kind(),
3346                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3347            ) && parameters
3348                .iter()
3349                .any(|parameter| parameter.as_str() == node_text(node, source))
3350                && Self::macro_formal_needs_statement_separator(node)
3351            {
3352                let point = node.end_byte().saturating_sub(body_offset);
3353                if point <= normalized.len() && !insertion_points.contains(&point) {
3354                    insertion_points.push(point);
3355                }
3356            }
3357            push_named_children_reversed(node, &mut stack);
3358        }
3359        if insertion_points.is_empty() {
3360            return None;
3361        }
3362        insertion_points.sort_unstable();
3363        let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3364        let mut original_offsets =
3365            Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3366        let mut next_insertion = 0;
3367        for (index, byte) in normalized.bytes().enumerate() {
3368            recovered.push(byte);
3369            original_offsets.push(normalized_offsets[index].clone());
3370            while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3371                recovered.push(b';');
3372                let boundary = normalized_offsets[index].end;
3373                original_offsets.push(boundary..boundary);
3374                next_insertion += 1;
3375            }
3376        }
3377        original_offsets.push(replacement.len()..replacement.len());
3378        let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3379        let parsed = parse_sentinel(
3380            MACRO_BODY_SENTINEL_PREFIX,
3381            SentinelInput {
3382                source: recovered,
3383                original_offsets,
3384            },
3385            MACRO_BODY_SENTINEL_SUFFIX,
3386        );
3387        let (source, tree, original_offsets) =
3388            (parsed.source, parsed.tree, parsed.original_offsets);
3389        if tree.root_node().has_error() {
3390            return None;
3391        }
3392        Some((source, tree, original_offsets.into_boxed_slice()))
3393    }
3394
3395    /// A formal is statement-shaped when the recovered tree places it directly
3396    /// at a statement boundary. This is the grammar recovery produced for
3397    /// replacement forms such as `if (condition) block`; an identifier inside
3398    /// an expression or call argument must not receive an artificial `;`.
3399    fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3400        let mut current = node;
3401        let mut crossed_recovery = false;
3402        while let Some(parent) = current.parent() {
3403            if parent.is_error() {
3404                crossed_recovery = true;
3405                current = parent;
3406                continue;
3407            }
3408            if matches!(
3409                parent.kind(),
3410                "call_expression"
3411                    | "argument_list"
3412                    | "field_expression"
3413                    | "binary_expression"
3414                    | "unary_expression"
3415                    | "assignment_expression"
3416                    | "conditional_expression"
3417                    | "parenthesized_expression"
3418                    | "subscript_expression"
3419            ) {
3420                return false;
3421            }
3422            if parent.kind() == "expression_statement" {
3423                return single_named_child_matching(parent, |child| !child.is_extra()).is_some();
3424            }
3425            if parent.kind() == "compound_statement" && current == node {
3426                return true;
3427            }
3428            if matches!(
3429                parent.kind(),
3430                "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3431            ) {
3432                return crossed_recovery
3433                    || parent.child_by_field_name("consequence") == Some(current);
3434            }
3435            if matches!(parent.kind(), "declaration" | "init_declarator") {
3436                return false;
3437            }
3438            current = parent;
3439        }
3440        false
3441    }
3442
3443    /// Identify fixed macro formals that retain an unambiguous type role even
3444    /// when an unrelated part of the replacement needs parser recovery.
3445    ///
3446    /// Variadic production macros commonly contain token pasting elsewhere in
3447    /// the body. The strict shared replacement parser must keep rejecting that
3448    /// body for expansion modeling, but a type declaration such as `TY col`
3449    /// remains usable when the node itself retains a structured type role.
3450    fn macro_type_parameter_indices(
3451        &self,
3452        key: &(ProjectFile, usize),
3453        parameters: &[String],
3454        replacement: &str,
3455    ) -> Option<Arc<[usize]>> {
3456        visibility_memo_value(
3457            &self.macro_type_parameters,
3458            &self.reads,
3459            key.clone(),
3460            || {
3461                #[cfg(any(test, feature = "test-support"))]
3462                self.macro_replacement_parse_count
3463                    .fetch_add(1, Ordering::Relaxed);
3464                if replacement.trim().is_empty() {
3465                    return None;
3466                }
3467                // Keep this parser permissive. Type-role extraction predates the
3468                // replacement-body model and deliberately survives unrelated
3469                // token-pasting or recovery elsewhere in a production macro.
3470                let parsed = parse_sentinel(
3471                    MACRO_BODY_SENTINEL_PREFIX,
3472                    SentinelInput::replacement(replacement),
3473                    MACRO_BODY_SENTINEL_SUFFIX,
3474                );
3475                let (source, tree, original_offsets) =
3476                    (parsed.source, parsed.tree, parsed.original_offsets);
3477                let body = ParsedReplacementBody {
3478                    source,
3479                    tree,
3480                    body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3481                    parameters: parameters.to_vec(),
3482                    original_offsets: original_offsets.into_boxed_slice(),
3483                };
3484                macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3485            },
3486        )
3487    }
3488
3489    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3490        let replacement = if node.kind() == "preproc_function_def" {
3491            function_macro_replacement_span(node, source)
3492                .and_then(|span| source.get(span))
3493                .map(str::to_owned)
3494                .or_else(|| {
3495                    node.child_by_field_name("value")
3496                        .map(|value| node_text(value, source).to_string())
3497                })
3498                .unwrap_or_default()
3499        } else {
3500            node.child_by_field_name("value")
3501                .map(|value| node_text(value, source).to_string())
3502                .unwrap_or_default()
3503        };
3504        if node.kind() == "preproc_def" {
3505            return MacroDefinition::Object { replacement };
3506        }
3507        let Some(parameters) = node.child_by_field_name("parameters") else {
3508            return MacroDefinition::Unsupported;
3509        };
3510        let variadic = (0..parameters.child_count()).any(|index| {
3511            parameters
3512                .child(index)
3513                .is_some_and(|child| child.kind() == "...")
3514        });
3515        let parameters = (0..parameters.named_child_count())
3516            .filter_map(|index| parameters.named_child(index))
3517            .map(|parameter| node_text(parameter, source).to_string())
3518            .collect::<Vec<_>>();
3519        if variadic {
3520            MacroDefinition::VariadicFunction {
3521                parameters,
3522                replacement,
3523            }
3524        } else {
3525            MacroDefinition::Function {
3526                parameters,
3527                replacement,
3528            }
3529        }
3530    }
3531
3532    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3533        visibility_memo_value(&self.macro_event_cells, &self.reads, file.clone(), || {
3534            Arc::new(VisibilityCell::new(self.reads.clone()))
3535        })
3536    }
3537
3538    fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3539        let names = visibility_memo_value(
3540            &self.macro_event_name_sets,
3541            &self.reads,
3542            file.clone(),
3543            || {
3544                let cell = self.macro_event_cell(file);
3545                let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
3546                Arc::new(
3547                    events
3548                        .iter()
3549                        .filter_map(|event| match event {
3550                            MacroEvent::Define { name, .. } => Some(name.clone()),
3551                            MacroEvent::Undef { .. }
3552                            | MacroEvent::Include { .. }
3553                            | MacroEvent::Invalidate { .. } => None,
3554                        })
3555                        .collect(),
3556                )
3557            },
3558        );
3559        names.contains(name)
3560    }
3561
3562    fn macro_environment_checkpoint_cell(
3563        &self,
3564        file: &ProjectFile,
3565    ) -> MacroEnvironmentCheckpointCell {
3566        visibility_memo_value(
3567            &self.macro_environment_checkpoints,
3568            &self.reads,
3569            file.clone(),
3570            || Arc::new(VisibilityCell::new(self.reads.clone())),
3571        )
3572    }
3573
3574    /// The environment of `file`'s macro events applied up to `before_byte`.
3575    pub fn macro_environment(
3576        &self,
3577        file: &ProjectFile,
3578        before_byte: usize,
3579    ) -> Arc<MacroEnvironment> {
3580        #[cfg(any(test, feature = "test-support"))]
3581        self.macro_environment_request_count
3582            .fetch_add(1, Ordering::Relaxed);
3583        let cell = self.macro_event_cell(file);
3584        let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
3585        let frontier = events.partition_point(|event| event.byte() < before_byte);
3586        let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3587        let checkpoints = checkpoint_cell
3588            .get_or_init(|| Arc::new(self.build_macro_environment_checkpoints(file, &events)));
3589        let checkpoint = checkpoints.at_or_before(frontier);
3590        if checkpoint.frontier == frontier {
3591            return Arc::clone(&checkpoint.environment);
3592        }
3593        #[cfg(any(test, feature = "test-support"))]
3594        self.macro_environment_copy_count
3595            .fetch_add(1, Ordering::Relaxed);
3596        let mut environment = checkpoint.environment.as_ref().clone();
3597        let mut include_stack = HashSet::from_iter([file.clone()]);
3598        for event in &events[checkpoint.frontier..frontier] {
3599            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3600        }
3601        Arc::new(environment)
3602    }
3603
3604    /// Apply `file`'s events once, keeping the environment at the prefixes
3605    /// [`MacroEnvironmentCheckpoints`] describes.
3606    fn build_macro_environment_checkpoints(
3607        &self,
3608        file: &ProjectFile,
3609        events: &[MacroEvent],
3610    ) -> MacroEnvironmentCheckpoints {
3611        #[cfg(any(test, feature = "test-support"))]
3612        self.macro_environment_checkpoint_build_count
3613            .fetch_add(1, Ordering::Relaxed);
3614        // The TU's build-proven defines hold from the first byte (#2011): they
3615        // are facts of the whole compile, so they seed the frontier-zero
3616        // checkpoint. A later explicit #undef event still overrides them
3617        // through `known_undefined_names`.
3618        let mut environment = MacroEnvironment {
3619            build_proven_defines: self
3620                .compile_proven_guards(file)
3621                .iter()
3622                .filter_map(|guard| match guard {
3623                    PreprocessorGuard::Defined(name) => Some(name.clone()),
3624                    _ => None,
3625                })
3626                .collect(),
3627            ..MacroEnvironment::default()
3628        };
3629        let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3630            frontier: 0,
3631            environment: Arc::new(environment.clone()),
3632        }];
3633        // Keep roughly one fixed stride's worth of checkpoints, while taking
3634        // a checkpoint at every event for event sets no larger than the fixed
3635        // stride. Generated C tables commonly have thousands of uses between
3636        // one #define and a trailing #undef; with a fixed stride those
3637        // identical frontier requests would all copy and replay the same
3638        // prefix.
3639        let checkpoint_stride = events
3640            .len()
3641            .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3642            .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3643        let mut include_stack = HashSet::from_iter([file.clone()]);
3644        for (index, event) in events.iter().enumerate() {
3645            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3646            let frontier = index + 1;
3647            if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3648                checkpoints.push(MacroEnvironmentCheckpoint {
3649                    frontier,
3650                    environment: Arc::new(environment.clone()),
3651                });
3652            }
3653        }
3654        MacroEnvironmentCheckpoints { checkpoints }
3655    }
3656
3657    /// Whether `name` is bound as a macro at `before_byte` in `file`,
3658    /// including a binding this environment cannot pin to one replacement
3659    /// (a conditional `#define`, or a function-like macro).
3660    ///
3661    /// [`Self::object_macro_replacement_at`] collapses every such binding to
3662    /// `None`, which is indistinguishable from "not a macro at all". A caller
3663    /// that must not read a macro token as an ordinary type name needs the two
3664    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
3665    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3666        self.macro_environment(file, before_byte)
3667            .binding(name)
3668            .is_some()
3669    }
3670
3671    pub fn macro_name_may_be_bound_at(
3672        &self,
3673        file: &ProjectFile,
3674        name: &str,
3675        before_byte: usize,
3676    ) -> bool {
3677        self.macro_environment(file, before_byte).may_bind(name)
3678    }
3679
3680    /// Whether the active macro binding at this reference is the requested
3681    /// indexed definition. Name equality alone is not enough because two
3682    /// headers can define the same macro for different translation units.
3683    pub fn macro_binding_matches_target_at(
3684        &self,
3685        analyzer: &CppGraphSource<'_>,
3686        file: &ProjectFile,
3687        name: &str,
3688        before_byte: usize,
3689        target: &CodeUnit,
3690    ) -> bool {
3691        let ranges = analyzer.ranges(target);
3692        let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3693        self.macro_binding_matches_target_declaration_at(
3694            file,
3695            name,
3696            before_byte,
3697            target.source(),
3698            &declaration_bytes,
3699        )
3700    }
3701
3702    /// Resolve declaration ranges once for inverse scans that test many call
3703    /// sites against one macro target. Walking a large generated syntax tree
3704    /// back to the same `#define` for every call site is otherwise quadratic
3705    /// in the number of top-level declarations.
3706    pub(crate) fn macro_declaration_bytes(
3707        &self,
3708        target: &CodeUnit,
3709        ranges: &[Range],
3710    ) -> Vec<usize> {
3711        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3712            return Vec::new();
3713        };
3714        ranges
3715            .iter()
3716            .filter_map(|range| {
3717                let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3718                while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3719                    node = node.parent()?;
3720                }
3721                Some(node.start_byte())
3722            })
3723            .collect()
3724    }
3725
3726    pub(crate) fn macro_binding_matches_target_declaration_at(
3727        &self,
3728        file: &ProjectFile,
3729        name: &str,
3730        before_byte: usize,
3731        target_source: &ProjectFile,
3732        target_declaration_bytes: &[usize],
3733    ) -> bool {
3734        let environment = self.macro_environment(file, before_byte);
3735        let Some(binding) = environment.binding(name) else {
3736            return false;
3737        };
3738        if binding.definition == MacroDefinition::Unsupported {
3739            return false;
3740        }
3741        // A normal header guard makes the replacement text conditional, but
3742        // it does not erase the definition site's source and byte identity.
3743        // Keep that identity even when expansion details are not exact.
3744        if binding.source != *target_source {
3745            return false;
3746        }
3747        target_declaration_bytes.contains(&binding.declaration_byte)
3748    }
3749
3750    /// Resolve an ordinary expression-position macro token at its exact byte.
3751    ///
3752    /// Calls and preprocessor-condition tokens have separate resolution
3753    /// surfaces. Declaration names, macro parameters, and labels are not
3754    /// references. Keeping that role policy here makes forward and both
3755    /// inverse graph builders consume the same activation verdict (#2093).
3756    pub fn resolve_ordinary_macro_reference(
3757        &self,
3758        analyzer: &CppGraphSource<'_>,
3759        file: &ProjectFile,
3760        node: Node<'_>,
3761        source: &str,
3762    ) -> OrdinaryMacroReferenceResolution {
3763        if !is_ordinary_macro_reference_node(node) {
3764            return OrdinaryMacroReferenceResolution::Missing;
3765        }
3766        let name = node_text(node, source);
3767        if name.is_empty() {
3768            return OrdinaryMacroReferenceResolution::Missing;
3769        }
3770        let visible = self
3771            .visible_identifier_candidates(file, name)
3772            .filter(|candidate| candidate.is_macro())
3773            .cloned()
3774            .collect::<Vec<_>>();
3775        let mut exact = Vec::new();
3776        for candidate in &visible {
3777            if self.macro_binding_matches_target_at(
3778                analyzer,
3779                file,
3780                name,
3781                node.start_byte(),
3782                candidate,
3783            ) && !exact
3784                .iter()
3785                .any(|existing| same_visible_symbol(existing, candidate))
3786            {
3787                exact.push(candidate.clone());
3788            }
3789        }
3790        match exact.len() {
3791            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3792            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3793            0 if !visible.is_empty()
3794                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3795            {
3796                OrdinaryMacroReferenceResolution::Ambiguous
3797            }
3798            0 => OrdinaryMacroReferenceResolution::Missing,
3799        }
3800    }
3801
3802    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
3803    ///
3804    /// The ordinary census deliberately skips every `ERROR` subtree. This
3805    /// separate, precision-only frontier admits only roles that retain enough
3806    /// structure for the C usage graph to interpret independently (#2089).
3807    /// Macro evidence comes from this visibility index at the exact byte; no
3808    /// source-text parsing or terminal-name fallback is used.
3809    pub fn recovered_c_reference_ranges(
3810        &self,
3811        file: &ProjectFile,
3812        root: Node<'_>,
3813        source: &str,
3814        limit: usize,
3815    ) -> RecoveredCReferenceRanges {
3816        if !is_c_source_file(file) {
3817            return RecoveredCReferenceRanges::Complete(Vec::new());
3818        }
3819        let mut ranges = Vec::new();
3820        let mut seen = HashSet::default();
3821        let mut stack = vec![(root, root.is_error())];
3822        while let Some((node, inside_error)) = stack.pop() {
3823            // A `macro_type_specifier` is the macro-declaration grammar's
3824            // structured form of the same evidence an `ERROR` carried: it is
3825            // admitted only where the parse would otherwise fail, so the type
3826            // it wraps keeps its structured references.
3827            let inside_error =
3828                inside_error || node.is_error() || node.kind() == "macro_type_specifier";
3829            if node.kind() == "preproc_arg" {
3830                // Tree-sitter keeps an object-like replacement opaque. The
3831                // inverse extractor uses the same parsed replacement helper;
3832                // retain its exact macro/type leaves for precision membership
3833                // even when the preprocessor node is outside ERROR recovery.
3834                let macro_value_kind = node.parent().and_then(|parent| {
3835                    (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3836                });
3837                if matches!(
3838                    macro_value_kind,
3839                    Some("preproc_def" | "preproc_function_def")
3840                ) {
3841                    let name = node_text(node, source);
3842                    if !name.is_empty()
3843                        && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3844                        && !push_recovered_c_range(
3845                            &mut ranges,
3846                            &mut seen,
3847                            node.start_byte(),
3848                            node.end_byte(),
3849                            node,
3850                            limit,
3851                        )
3852                    {
3853                        return RecoveredCReferenceRanges::LimitExceeded;
3854                    }
3855                }
3856                if macro_value_kind == Some("preproc_def") {
3857                    for reference in object_macro_replacement_type_references(node, source) {
3858                        for range in reference.component_ranges {
3859                            let visible = self
3860                                .visible_identifier_candidates(file, &source[range.clone()])
3861                                .any(|candidate| candidate.is_class() || candidate.is_module());
3862                            if visible
3863                                && !push_recovered_c_range(
3864                                    &mut ranges,
3865                                    &mut seen,
3866                                    range.start,
3867                                    range.end,
3868                                    node,
3869                                    limit,
3870                                )
3871                            {
3872                                return RecoveredCReferenceRanges::LimitExceeded;
3873                            }
3874                        }
3875                    }
3876                }
3877            }
3878            if inside_error
3879                && recovered_c_reference_node(self, file, node, source)
3880                && !push_recovered_c_range(
3881                    &mut ranges,
3882                    &mut seen,
3883                    node.start_byte(),
3884                    node.end_byte(),
3885                    node,
3886                    limit,
3887                )
3888            {
3889                return RecoveredCReferenceRanges::LimitExceeded;
3890            }
3891            let mut cursor = node.walk();
3892            for child in node.named_children(&mut cursor) {
3893                stack.push((child, inside_error));
3894            }
3895        }
3896        ranges.sort_unstable();
3897        RecoveredCReferenceRanges::Complete(ranges)
3898    }
3899
3900    /// Whether this target is an indexed macro visible from this file.
3901    ///
3902    /// An unresolved conditional can make more than one same-name macro a
3903    /// possible active binding. Each possible target can keep the site as an
3904    /// unproven hit. A macro in an unrelated translation unit stays excluded.
3905    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3906        self.visible_identifier_candidates(file, target.identifier())
3907            .filter(|candidate| candidate.is_macro())
3908            .any(|candidate| {
3909                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3910            })
3911    }
3912
3913    pub fn object_macro_replacement_at(
3914        &self,
3915        file: &ProjectFile,
3916        name: &str,
3917        before_byte: usize,
3918    ) -> Option<String> {
3919        let environment = self.macro_environment(file, before_byte);
3920        let binding = environment.binding(name)?;
3921        if !binding.exact {
3922            return None;
3923        }
3924        match &binding.definition {
3925            MacroDefinition::Object { replacement } => Some(replacement.clone()),
3926            MacroDefinition::Function { .. }
3927            | MacroDefinition::VariadicFunction { .. }
3928            | MacroDefinition::Unsupported => None,
3929        }
3930    }
3931
3932    fn apply_macro_events(
3933        &self,
3934        file: &ProjectFile,
3935        environment: &mut MacroEnvironment,
3936        include_stack: &mut HashSet<ProjectFile>,
3937    ) {
3938        if !include_stack.insert(file.clone()) {
3939            return;
3940        }
3941        if self.cpp.prepared_syntax(self.token, file).is_none() {
3942            environment.mark_unknown_names(file, 0);
3943            include_stack.remove(file);
3944            return;
3945        }
3946        match self.macro_include_protection(file) {
3947            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3948                Some(binding) if binding.is_exact() => {
3949                    include_stack.remove(file);
3950                    return;
3951                }
3952                Some(_) | None if environment.unknown_names => {
3953                    let mut ambiguous_seen = HashSet::default();
3954                    self.mark_macro_events_ambiguous(
3955                        file,
3956                        environment,
3957                        &mut ambiguous_seen,
3958                        file,
3959                        0,
3960                    );
3961                    include_stack.remove(file);
3962                    return;
3963                }
3964                Some(_) => {
3965                    let mut ambiguous_seen = HashSet::default();
3966                    self.mark_macro_events_ambiguous(
3967                        file,
3968                        environment,
3969                        &mut ambiguous_seen,
3970                        file,
3971                        0,
3972                    );
3973                    include_stack.remove(file);
3974                    return;
3975                }
3976                None => {}
3977            },
3978            MacroIncludeProtection::PragmaOnce => {
3979                if !environment.applied_pragma_once_files.insert(file.clone()) {
3980                    include_stack.remove(file);
3981                    return;
3982                }
3983                if environment.maybe_applied_pragma_once_files.remove(file) {
3984                    // A prior conditional include may already have consumed the pragma-once
3985                    // header. This unconditional include guarantees it is consumed now, but
3986                    // cannot prove whether its events occur before or after intervening local
3987                    // macro changes, so preserve the union as ambiguous.
3988                    let mut ambiguous_seen = HashSet::default();
3989                    environment.applied_pragma_once_files.remove(file);
3990                    self.mark_macro_events_ambiguous(
3991                        file,
3992                        environment,
3993                        &mut ambiguous_seen,
3994                        file,
3995                        0,
3996                    );
3997                    environment.maybe_applied_pragma_once_files.remove(file);
3998                    environment.applied_pragma_once_files.insert(file.clone());
3999                    include_stack.remove(file);
4000                    return;
4001                }
4002            }
4003            MacroIncludeProtection::None => {}
4004        }
4005        let cell = self.macro_event_cell(file);
4006        let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
4007        for event in events.iter() {
4008            self.apply_macro_event(file, event, environment, include_stack);
4009        }
4010        include_stack.remove(file);
4011    }
4012
4013    fn apply_macro_event(
4014        &self,
4015        file: &ProjectFile,
4016        event: &MacroEvent,
4017        environment: &mut MacroEnvironment,
4018        include_stack: &mut HashSet<ProjectFile>,
4019    ) {
4020        #[cfg(any(test, feature = "test-support"))]
4021        self.macro_event_application_count
4022            .fetch_add(1, Ordering::Relaxed);
4023        match event {
4024            MacroEvent::Define {
4025                name,
4026                binding,
4027                conditionals,
4028                byte,
4029            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
4030                Some(true) => environment.insert(name.clone(), binding.clone()),
4031                Some(false) => {}
4032                None => Self::merge_conditional_macro_definition(
4033                    environment,
4034                    name,
4035                    binding,
4036                    file,
4037                    *byte,
4038                ),
4039            },
4040            MacroEvent::Undef {
4041                name,
4042                conditionals,
4043                byte,
4044            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
4045                Some(true) => environment.remove(name),
4046                Some(false) => {}
4047                None => {
4048                    if environment.binding(name).is_some() {
4049                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
4050                    }
4051                }
4052            },
4053            MacroEvent::Include {
4054                targets,
4055                conditionals,
4056                byte,
4057            } => {
4058                let condition =
4059                    self.macro_event_condition_value(file, *byte, environment, conditionals);
4060                if condition == Some(false) {
4061                    return;
4062                }
4063                if targets.is_empty() {
4064                    environment.mark_unknown_names(file, *byte);
4065                    return;
4066                }
4067                if condition.is_none() || targets.len() > 1 {
4068                    let mut ambiguous_seen = HashSet::default();
4069                    for target in targets {
4070                        self.mark_macro_events_ambiguous(
4071                            target,
4072                            environment,
4073                            &mut ambiguous_seen,
4074                            file,
4075                            *byte,
4076                        );
4077                    }
4078                } else if let Some(target) = targets.first() {
4079                    self.apply_macro_events(target, environment, include_stack);
4080                }
4081            }
4082            MacroEvent::Invalidate { byte } => {
4083                for binding in environment.bindings.values_mut() {
4084                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
4085                }
4086            }
4087        }
4088    }
4089
4090    /// Evaluate the structured conditional path that owns one macro event.
4091    ///
4092    /// `Some(true)` and `Some(false)` are proofs from exact macro bindings at
4093    /// this source byte. `None` preserves the old conditional merge when a
4094    /// build/configuration input or an unsupported expression is involved.
4095    ///
4096    /// `conditionals` is the event's own [`OwningPreprocessorConditionals`]:
4097    /// which ancestors structurally own the event was decided when the events
4098    /// were collected, so all this walk does is find those nodes again and ask
4099    /// the environment what their conditions are worth here.
4100    fn macro_event_condition_value(
4101        &self,
4102        file: &ProjectFile,
4103        event_byte: usize,
4104        environment: &MacroEnvironment,
4105        conditionals: &OwningPreprocessorConditionals,
4106    ) -> Option<bool> {
4107        if conditionals.is_empty() {
4108            return Some(true);
4109        }
4110        let prepared = self.cpp.prepared_syntax(self.token, file)?;
4111        let source = prepared.source();
4112        let root = prepared.tree().root_node();
4113        let descendant = root.descendant_for_byte_range(
4114            event_byte,
4115            event_byte.saturating_add(1).min(source.len()),
4116        )?;
4117        let mut unknown = false;
4118        let mut current = descendant.parent();
4119        while let Some(conditional) = current {
4120            if matches!(
4121                conditional.kind(),
4122                "preproc_if" | "preproc_ifdef" | "preproc_elif"
4123            ) && conditionals.contains(&conditional.start_byte())
4124            {
4125                match self.preprocessor_conditional_branch_value(
4126                    conditional,
4127                    descendant,
4128                    source,
4129                    environment,
4130                ) {
4131                    Some(true) => {}
4132                    Some(false) => return Some(false),
4133                    None => unknown = true,
4134                }
4135            }
4136            current = conditional.parent();
4137        }
4138        (!unknown).then_some(true)
4139    }
4140
4141    fn preprocessor_conditional_branch_value(
4142        &self,
4143        conditional: Node<'_>,
4144        descendant: Node<'_>,
4145        source: &str,
4146        environment: &MacroEnvironment,
4147    ) -> Option<bool> {
4148        let value = match conditional.kind() {
4149            "preproc_ifdef" => {
4150                let name = conditional.child_by_field_name("name")?;
4151                let defined = self.macro_name_defined_value(environment, node_text(name, source));
4152                match conditional.child(0)?.kind() {
4153                    "#ifdef" => defined,
4154                    "#ifndef" => defined.map(|defined| !defined),
4155                    _ => None,
4156                }
4157            }
4158            "preproc_if" | "preproc_elif" => conditional
4159                .child_by_field_name("condition")
4160                .and_then(|condition| {
4161                    self.preprocessor_integer_value(
4162                        condition,
4163                        source,
4164                        environment,
4165                        &mut Vec::new(),
4166                        0,
4167                    )
4168                })
4169                .map(|value| value != 0),
4170            _ => unreachable!(),
4171        };
4172        match preprocessor_branch_of_descendant(
4173            conditional,
4174            descendant,
4175            &mut PreprocessorContextCache::default(),
4176        ) {
4177            PreprocessorBranch::Consequence => value,
4178            PreprocessorBranch::Structured(_) | PreprocessorBranch::DisplacedElse => {
4179                value.map(|value| !value)
4180            }
4181            PreprocessorBranch::Unreadable => None,
4182        }
4183    }
4184
4185    fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
4186        if environment.known_undefined_names.contains(name) {
4187            return Some(false);
4188        }
4189        if let Some(binding) = environment.binding(name) {
4190            return binding.is_exact().then_some(true);
4191        }
4192        environment
4193            .build_proven_defines
4194            .contains(name)
4195            .then_some(true)
4196    }
4197
4198    fn preprocessor_integer_value(
4199        &self,
4200        expression: Node<'_>,
4201        source: &str,
4202        environment: &MacroEnvironment,
4203        expansion_stack: &mut Vec<(ProjectFile, usize)>,
4204        depth: usize,
4205    ) -> Option<i128> {
4206        // Macro replacement graphs can cycle. This explicit bound makes the
4207        // otherwise recursive AST evaluation stack-safe for hostile input.
4208        if depth >= 64 {
4209            return None;
4210        }
4211        match expression.kind() {
4212            "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
4213            "identifier" | "type_identifier" => {
4214                let binding = environment.binding(node_text(expression, source))?;
4215                if !binding.is_exact() {
4216                    return None;
4217                }
4218                let MacroDefinition::Object { replacement } = &binding.definition else {
4219                    return None;
4220                };
4221                let identity = (binding.source.clone(), binding.declaration_byte);
4222                if expansion_stack.contains(&identity) {
4223                    return None;
4224                }
4225                expansion_stack.push(identity);
4226                let parsed = self.parsed_macro_replacement(binding, replacement);
4227                let value = match parsed.as_ref() {
4228                    ParsedMacroReplacement::Parsed {
4229                        source: replacement_source,
4230                        tree,
4231                    } => first_descendant_of_kind(tree.root_node(), "call_expression")
4232                        .and_then(|call| call.child_by_field_name("arguments"))
4233                        .and_then(|arguments| argument_children(arguments).next())
4234                        .and_then(|argument| {
4235                            self.preprocessor_integer_value(
4236                                argument,
4237                                replacement_source,
4238                                environment,
4239                                expansion_stack,
4240                                depth + 1,
4241                            )
4242                        }),
4243                    ParsedMacroReplacement::Unsupported => None,
4244                };
4245                expansion_stack.pop();
4246                value
4247            }
4248            "preproc_defined" => {
4249                let mut cursor = expression.walk();
4250                let name = expression
4251                    .named_children(&mut cursor)
4252                    .find(|child| child.kind() == "identifier")?;
4253                self.macro_name_defined_value(environment, node_text(name, source))
4254                    .map(i128::from)
4255            }
4256            "parenthesized_expression" => expression.named_child(0).and_then(|child| {
4257                self.preprocessor_integer_value(
4258                    child,
4259                    source,
4260                    environment,
4261                    expansion_stack,
4262                    depth + 1,
4263                )
4264            }),
4265            "unary_expression" => {
4266                let operator = expression.child_by_field_name("operator")?.kind();
4267                let argument = expression.child_by_field_name("argument")?;
4268                let value = self.preprocessor_integer_value(
4269                    argument,
4270                    source,
4271                    environment,
4272                    expansion_stack,
4273                    depth + 1,
4274                )?;
4275                match operator {
4276                    "+" => Some(value),
4277                    "-" => value.checked_neg(),
4278                    "!" => Some(i128::from(value == 0)),
4279                    "~" => Some(!value),
4280                    _ => None,
4281                }
4282            }
4283            "binary_expression" => {
4284                let left = self.preprocessor_integer_value(
4285                    expression.child_by_field_name("left")?,
4286                    source,
4287                    environment,
4288                    expansion_stack,
4289                    depth + 1,
4290                )?;
4291                let right = self.preprocessor_integer_value(
4292                    expression.child_by_field_name("right")?,
4293                    source,
4294                    environment,
4295                    expansion_stack,
4296                    depth + 1,
4297                )?;
4298                match expression.child_by_field_name("operator")?.kind() {
4299                    "+" => left.checked_add(right),
4300                    "-" => left.checked_sub(right),
4301                    "*" => left.checked_mul(right),
4302                    "/" => left.checked_div(right),
4303                    "%" => left.checked_rem(right),
4304                    "<<" => u32::try_from(right)
4305                        .ok()
4306                        .and_then(|shift| left.checked_shl(shift)),
4307                    ">>" => u32::try_from(right)
4308                        .ok()
4309                        .and_then(|shift| left.checked_shr(shift)),
4310                    "<" => Some(i128::from(left < right)),
4311                    "<=" => Some(i128::from(left <= right)),
4312                    ">" => Some(i128::from(left > right)),
4313                    ">=" => Some(i128::from(left >= right)),
4314                    "==" => Some(i128::from(left == right)),
4315                    "!=" => Some(i128::from(left != right)),
4316                    "&" => Some(left & right),
4317                    "|" => Some(left | right),
4318                    "^" => Some(left ^ right),
4319                    "&&" => Some(i128::from(left != 0 && right != 0)),
4320                    "||" => Some(i128::from(left != 0 || right != 0)),
4321                    _ => None,
4322                }
4323            }
4324            _ => None,
4325        }
4326    }
4327
4328    fn mark_macro_events_ambiguous(
4329        &self,
4330        file: &ProjectFile,
4331        environment: &mut MacroEnvironment,
4332        include_stack: &mut HashSet<ProjectFile>,
4333        conditional_file: &ProjectFile,
4334        conditional_byte: usize,
4335    ) {
4336        if !include_stack.insert(file.clone()) {
4337            return;
4338        }
4339        if self.cpp.prepared_syntax(self.token, file).is_none() {
4340            environment.mark_unknown_names(conditional_file, conditional_byte);
4341            return;
4342        }
4343        match self.macro_include_protection(file) {
4344            MacroIncludeProtection::MacroGuard(guard) => {
4345                if environment
4346                    .binding(&guard)
4347                    .is_some_and(MacroBinding::is_exact)
4348                {
4349                    return;
4350                }
4351            }
4352            MacroIncludeProtection::PragmaOnce => {
4353                if environment.applied_pragma_once_files.contains(file) {
4354                    return;
4355                }
4356                environment
4357                    .maybe_applied_pragma_once_files
4358                    .insert(file.clone());
4359            }
4360            MacroIncludeProtection::None => {}
4361        }
4362        let cell = self.macro_event_cell(file);
4363        let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
4364        for event in events.iter() {
4365            #[cfg(any(test, feature = "test-support"))]
4366            self.macro_event_application_count
4367                .fetch_add(1, Ordering::Relaxed);
4368            match event {
4369                MacroEvent::Define { name, binding, .. } => {
4370                    Self::merge_conditional_macro_definition(
4371                        environment,
4372                        name,
4373                        binding,
4374                        conditional_file,
4375                        conditional_byte,
4376                    );
4377                }
4378                MacroEvent::Undef { name, .. } => {
4379                    if environment.binding(name).is_some() {
4380                        environment.insert(
4381                            name.clone(),
4382                            MacroBinding::ambiguous(conditional_file, conditional_byte),
4383                        );
4384                    } else {
4385                        environment.remove_known_undefined(name);
4386                    }
4387                }
4388                MacroEvent::Include { targets, .. } => {
4389                    if targets.is_empty() {
4390                        environment.mark_unknown_names(conditional_file, conditional_byte);
4391                        continue;
4392                    }
4393                    for target in targets {
4394                        self.mark_macro_events_ambiguous(
4395                            target,
4396                            environment,
4397                            include_stack,
4398                            conditional_file,
4399                            conditional_byte,
4400                        );
4401                    }
4402                }
4403                MacroEvent::Invalidate { .. } => {
4404                    for binding in environment.bindings.values_mut() {
4405                        *binding = MacroBinding::uncertain_from(
4406                            binding,
4407                            conditional_file,
4408                            conditional_byte,
4409                        );
4410                    }
4411                }
4412            }
4413        }
4414    }
4415
4416    fn merge_conditional_macro_definition(
4417        environment: &mut MacroEnvironment,
4418        name: &str,
4419        possible_binding: &MacroBinding,
4420        conditional_file: &ProjectFile,
4421        conditional_byte: usize,
4422    ) {
4423        // A conditional include can revisit an already-active guarded header.
4424        // If the possible branch defines the exact same macro, both outcomes
4425        // leave the binding unchanged; degrading it to Unknown would discard
4426        // proof because of an unrelated unresolved macro name (#2092).
4427        if environment.binding(name).is_some_and(|current| {
4428            current.definition != MacroDefinition::Unsupported
4429                && current.definition == possible_binding.definition
4430        }) {
4431            return;
4432        }
4433        environment.insert(
4434            name.to_string(),
4435            MacroBinding::ambiguous(conditional_file, conditional_byte),
4436        );
4437    }
4438
4439    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4440        let value = self
4441            .macro_include_protection_cells
4442            .read(&self.reads.memo_attribution(), |cells, capture| {
4443                let cell = cells
4444                    .lock()
4445                    .expect("C++ include protection cache poisoned")
4446                    .entry(file.clone())
4447                    .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4448                    .clone();
4449                Ok::<_, std::convert::Infallible>(cell.get_or_build_pool_independent(|| {
4450                    capture.build(|| {
4451                        self.cpp.prepared_syntax(self.token, file).map_or(
4452                            MacroIncludeProtection::None,
4453                            |prepared| {
4454                                top_level_macro_include_protection(
4455                                    prepared.tree().root_node(),
4456                                    prepared.source(),
4457                                )
4458                            },
4459                        )
4460                    })
4461                }))
4462            })
4463            .expect("infallible include protection build");
4464        (*value).clone()
4465    }
4466
4467    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4468        collect_macro_events(self.cpp, self.token, file)
4469    }
4470
4471    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4472        visibility_memo_value(
4473            &self.ordinary_type_import_cells,
4474            &self.reads,
4475            file.clone(),
4476            || Arc::new(EffectiveUsingIndex::new(file.clone(), self.reads.clone())),
4477        )
4478    }
4479
4480    pub fn project_using_index(
4481        &self,
4482        build: impl FnOnce() -> ProjectUsingIndex,
4483    ) -> Arc<ProjectUsingIndex> {
4484        visibility_once_value(&self.project_using_index, &self.reads, || Arc::new(build()))
4485    }
4486
4487    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4488        let mut files = self
4489            .visible_source_files_by_root
4490            .values()
4491            .flatten()
4492            .cloned()
4493            .collect::<HashSet<_>>()
4494            .into_iter()
4495            .collect::<Vec<_>>();
4496        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4497        files
4498    }
4499
4500    /// The namespace aliases written in `root`'s visible source files, by name.
4501    pub(super) fn namespace_alias_cell(
4502        &self,
4503        root: &ProjectFile,
4504    ) -> super::namespace_alias::NamespaceAliasCell {
4505        visibility_memo_value(
4506            &self.namespace_alias_sites,
4507            &self.reads,
4508            root.clone(),
4509            || Arc::new(VisibilityCell::new(self.reads.clone())),
4510        )
4511    }
4512
4513    /// `root` and every source file its include closure makes visible.
4514    pub(super) fn visible_source_files(&self, root: &ProjectFile) -> Vec<ProjectFile> {
4515        self.visible_source_files_by_root.get(root).map_or_else(
4516            || vec![root.clone()],
4517            |files| {
4518                files
4519                    .iter()
4520                    .chain([root])
4521                    .cloned()
4522                    .collect::<HashSet<_>>()
4523                    .into_iter()
4524                    .collect()
4525            },
4526        )
4527    }
4528
4529    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4530        self.visible_source_files_by_root
4531            .get(root)
4532            .is_some_and(|files| files.contains(source))
4533    }
4534
4535    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4536        try_visibility_memo_value(
4537            &self.visible_parser_alias_name_sets,
4538            &self.reads,
4539            file.clone(),
4540            || {
4541                let mut names = HashSet::default();
4542                let visible_files = self
4543                    .visible_source_files_by_root
4544                    .get(file)
4545                    .cloned()
4546                    .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4547                for visible_file in visible_files {
4548                    let aliases = self
4549                        .canonical_alias_cell(self.cpp, &visible_file)
4550                        .ok_or(())?;
4551                    for alias in aliases.get().expect("published canonical aliases").iter() {
4552                        names.insert(alias.name.clone());
4553                    }
4554                }
4555                #[cfg(any(test, feature = "test-support"))]
4556                self.visible_parser_alias_name_set_build_count
4557                    .fetch_add(1, Ordering::Relaxed);
4558                Ok::<_, ()>(Arc::new(names))
4559            },
4560        )
4561        .ok()
4562        .is_some_and(|names| names.contains(name))
4563    }
4564
4565    pub fn parser_alias_name_may_resolve_to_target(
4566        &self,
4567        file: &ProjectFile,
4568        alias_name: &str,
4569        target: &CodeUnit,
4570    ) -> bool {
4571        let started = std::time::Instant::now();
4572        self.parser_alias_fallback_calls
4573            .fetch_add(1, Ordering::Relaxed);
4574        let matched = visibility_memo_value(
4575            &self.parser_alias_target_matches,
4576            &self.reads,
4577            (
4578                file.clone(),
4579                alias_name.to_string(),
4580                logical_symbol_key(target),
4581            ),
4582            || {
4583                any_visible_source_file_matches(
4584                    &self.visible_source_files_by_root,
4585                    file,
4586                    |visible_file| {
4587                        self.parser_alias_fallback_files
4588                            .fetch_add(1, Ordering::Relaxed);
4589                        self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4590                    },
4591                )
4592            },
4593        );
4594        self.parser_alias_fallback_elapsed_micros.fetch_add(
4595            started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4596            Ordering::Relaxed,
4597        );
4598        matched
4599    }
4600
4601    fn callable_arities_for_target(
4602        &self,
4603        analyzer: &CppGraphSource<'_>,
4604        cpp: &dyn CppSource,
4605        file: &ProjectFile,
4606        prepared: &PreparedSyntaxTree,
4607        spec: &TargetSpec,
4608    ) -> Vec<ActivatedCallableArity> {
4609        let Some(signature) = spec.target.signature() else {
4610            return Vec::new();
4611        };
4612        let Some(candidates) = self
4613            .visible_by_identifier
4614            .get(file)
4615            .and_then(|by_name| by_name.get(&spec.member_name))
4616        else {
4617            return Vec::new();
4618        };
4619        let differing_candidates = candidates
4620            .iter()
4621            .filter(|candidate| {
4622                candidate.is_function()
4623                    && candidate.fq_name() == spec.target.fq_name()
4624                    && candidate.signature() == Some(signature)
4625            })
4626            .filter_map(|candidate| {
4627                analyzer
4628                    .signature_metadata(candidate)
4629                    .into_iter()
4630                    .find_map(|metadata| metadata.callable_arity())
4631                    .filter(|arity| Some(*arity) != spec.callable_arity)
4632                    .map(|arity| (candidate, arity))
4633            })
4634            .collect::<Vec<_>>();
4635        if differing_candidates.is_empty() {
4636            return Vec::new();
4637        }
4638        let mut arities = Vec::with_capacity(differing_candidates.len());
4639        // The activation ranges here describe the whole file rather than one
4640        // reference, so only file-level language and build facts are known.
4641        let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
4642            self.effective_reference_guards(file, guards)
4643        };
4644        let reference = CallableReferenceContext {
4645            effective_guards: &effective_guards,
4646            file_guards: OnceCell::new(),
4647            file,
4648            position: None,
4649        };
4650        for (candidate, candidate_arity) in differing_candidates {
4651            let declaration_activation =
4652                callable_declaration_activation_in_file(analyzer, candidate, &reference);
4653            let Some(declaration_activation) = declaration_activation else {
4654                continue;
4655            };
4656            let activation_byte = if candidate.source() == file {
4657                Some(declaration_activation)
4658            } else {
4659                self.include_activation_for_source(cpp, file, prepared, candidate.source())
4660            };
4661            if let Some(activation_byte) = activation_byte {
4662                arities.push(ActivatedCallableArity {
4663                    activation_byte,
4664                    arity: candidate_arity,
4665                });
4666            }
4667        }
4668        arities
4669    }
4670
4671    fn callable_parameter_macro_arity(
4672        &self,
4673        target: &CodeUnit,
4674        parameter_types: Option<&[String]>,
4675    ) -> Option<CallableArity> {
4676        let parameter_types = parameter_types?;
4677        let [macro_name] = parameter_types else {
4678            return None;
4679        };
4680        if macro_name.is_empty()
4681            || !macro_name
4682                .chars()
4683                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4684        {
4685            return None;
4686        }
4687        let cache_key = (target.source().clone(), macro_name.clone());
4688        visibility_memo_value(
4689            &self.callable_parameter_macro_arities,
4690            &self.reads,
4691            cache_key,
4692            || {
4693                let mut visible_files = HashSet::default();
4694                collect_include_closure(
4695                    &self.cpp_source(),
4696                    &self.cpp.include_target_index(),
4697                    target.source(),
4698                    &mut visible_files,
4699                    None,
4700                );
4701                let mut arities = Vec::new();
4702                for visible_file in visible_files {
4703                    let cell = self.macro_event_cell(&visible_file);
4704                    for event in cell
4705                        .get_or_init(|| Arc::from(self.collect_macro_events(&visible_file)))
4706                        .iter()
4707                    {
4708                        let MacroEvent::Define { name, binding, .. } = event else {
4709                            continue;
4710                        };
4711                        if name != macro_name {
4712                            continue;
4713                        }
4714                        let MacroDefinition::Object { replacement } = &binding.definition else {
4715                            continue;
4716                        };
4717                        let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4718                            continue;
4719                        };
4720                        if !arities.contains(&arity) {
4721                            arities.push(arity);
4722                        }
4723                    }
4724                }
4725                let required = arities
4726                    .iter()
4727                    .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4728                    .min()?;
4729                let total = arities.iter().map(|arity| arity.total()).max()?;
4730                let repeated = arities
4731                    .iter()
4732                    .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4733                // Preprocessor conditions can leave more than one object-like parameter
4734                // bundle active in the target header's include closure. Preserve their
4735                // conservative callable envelope instead of choosing whichever definition
4736                // happened to be visited first.
4737                Some(CallableArity::new(required, total, repeated))
4738            },
4739        )
4740    }
4741
4742    pub fn include_activation_for_source(
4743        &self,
4744        cpp: &dyn CppSource,
4745        file: &ProjectFile,
4746        prepared: &PreparedSyntaxTree,
4747        donor_source: &ProjectFile,
4748    ) -> Option<usize> {
4749        visibility_memo_value(
4750            &self.include_activation_cells,
4751            &self.reads,
4752            (file.clone(), donor_source.clone()),
4753            || {
4754                #[cfg(any(test, feature = "test-support"))]
4755                self.include_activation_build_count
4756                    .fetch_add(1, Ordering::Relaxed);
4757                let effective = OnceCell::new();
4758                find_include_activation(cpp, self.token, file, prepared, donor_source, &|| {
4759                    effective
4760                        .get_or_init(|| {
4761                            self.effective_reference_guards(file, Some(&HashSet::default()))
4762                        })
4763                        .as_ref()
4764                })
4765            },
4766        )
4767    }
4768
4769    pub fn conditional_include_projections_for_source(
4770        &self,
4771        file: &ProjectFile,
4772        prepared: &PreparedSyntaxTree,
4773        donor_source: &ProjectFile,
4774    ) -> Arc<[ConditionalIncludeProjection]> {
4775        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4776        let index = visibility_memo_value(
4777            &self.conditional_include_projection_cells,
4778            &self.reads,
4779            file.clone(),
4780            || {
4781                #[cfg(any(test, feature = "test-support"))]
4782                self.conditional_include_projection_index_build_count
4783                    .fetch_add(1, Ordering::Relaxed);
4784                let effective = OnceCell::new();
4785                Arc::new(find_conditional_include_projection_index(
4786                    self.cpp,
4787                    self.token,
4788                    file,
4789                    prepared,
4790                    &|| {
4791                        effective
4792                            .get_or_init(|| {
4793                                self.effective_reference_guards(file, Some(&HashSet::default()))
4794                            })
4795                            .as_ref()
4796                    },
4797                    &|| {
4798                        #[cfg(any(test, feature = "test-support"))]
4799                        self.conditional_include_projection_state_count
4800                            .fetch_add(1, Ordering::Relaxed);
4801                    },
4802                ))
4803            },
4804        );
4805        index
4806            .get(donor_source)
4807            .cloned()
4808            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4809    }
4810
4811    fn include_language_guard_is_stable(&self, file: &ProjectFile) -> bool {
4812        let mut macro_events = |file: &ProjectFile| {
4813            let cell = self.macro_event_cell(file);
4814            cell.get_or_init(|| Arc::from(self.collect_macro_events(file)))
4815                .as_ref()
4816                .to_vec()
4817                .into_boxed_slice()
4818        };
4819        include_language_guard_is_stable_with(
4820            self.cpp,
4821            self.token,
4822            file,
4823            &mut macro_events,
4824            &mut |file: &ProjectFile, prepared: &PreparedSyntaxTree| {
4825                self.structured_include_facts(file, prepared)
4826            },
4827        )
4828    }
4829
4830    #[cfg(any(test, feature = "test-support"))]
4831    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4832        (
4833            self.conditional_include_projection_index_build_count
4834                .load(Ordering::Relaxed),
4835            self.conditional_include_projection_state_count
4836                .load(Ordering::Relaxed),
4837        )
4838    }
4839
4840    #[cfg(any(test, feature = "test-support"))]
4841    pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4842        self.conditional_include_target_state_count
4843            .load(Ordering::Relaxed)
4844    }
4845
4846    #[cfg(any(test, feature = "test-support"))]
4847    pub fn include_activation_build_count_for_test(&self) -> usize {
4848        self.include_activation_build_count.load(Ordering::Relaxed)
4849    }
4850
4851    #[cfg(any(test, feature = "test-support"))]
4852    pub fn note_using_donor_activation_for_test(&self) {
4853        self.using_donor_activation_count
4854            .fetch_add(1, Ordering::Relaxed);
4855    }
4856
4857    #[cfg(not(any(test, feature = "test-support")))]
4858    pub fn note_using_donor_activation_for_test(&self) {}
4859
4860    #[cfg(any(test, feature = "test-support"))]
4861    pub fn note_using_namespace_lookup_for_test(&self) {
4862        self.using_namespace_lookup_count
4863            .fetch_add(1, Ordering::Relaxed);
4864    }
4865
4866    #[cfg(not(any(test, feature = "test-support")))]
4867    pub fn note_using_namespace_lookup_for_test(&self) {}
4868
4869    #[cfg(any(test, feature = "test-support"))]
4870    pub fn note_using_name_candidate_inspection_for_test(&self) {
4871        self.using_name_candidate_inspection_count
4872            .fetch_add(1, Ordering::Relaxed);
4873    }
4874
4875    #[cfg(not(any(test, feature = "test-support")))]
4876    pub fn note_using_name_candidate_inspection_for_test(&self) {}
4877
4878    #[cfg(any(test, feature = "test-support"))]
4879    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4880        (
4881            self.using_donor_activation_count.load(Ordering::Relaxed),
4882            self.using_namespace_lookup_count.load(Ordering::Relaxed),
4883            self.callable_reference_spec_build_count
4884                .load(Ordering::Relaxed),
4885            self.using_name_candidate_inspection_count
4886                .load(Ordering::Relaxed),
4887        )
4888    }
4889
4890    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4891        file == target.source()
4892            || self
4893                .visible_by_file
4894                .get(file)
4895                .is_some_and(|visible| visible.contains(target))
4896    }
4897
4898    /// Whether some declaration of `declaration`'s logical symbol is visible at
4899    /// `reference_byte` in `file`.
4900    ///
4901    /// The question is asked of the *logical* symbol, not of the physical unit:
4902    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
4903    /// and it does not have to be - what makes the call legal is the header
4904    /// declaration that the reference file does include. Reading that relation
4905    /// through `same_logical_callable` rather than through signature strings is
4906    /// the same #2010 correction the gates make, and it matters here because
4907    /// the body and the declaration are exactly the pair that spells one
4908    /// parameter type two ways.
4909    pub fn declaration_visible_at(
4910        &self,
4911        analyzer: &CppGraphSource<'_>,
4912        file: &ProjectFile,
4913        declaration: &CodeUnit,
4914        reference_byte: usize,
4915    ) -> bool {
4916        let reference_guards = OnceCell::new();
4917        // A prototype must itself be active. Another same-signature prototype
4918        // cannot select its include branch or compilation dialect for it.
4919        if declaration.is_callable() && !callable_target_has_definition_body(analyzer, declaration)
4920        {
4921            return self.physical_declaration_visible_at(
4922                analyzer,
4923                file,
4924                declaration,
4925                reference_byte,
4926                &reference_guards,
4927            );
4928        }
4929        self.visible_identifier_candidates(file, declaration.identifier())
4930            .filter(|candidate| {
4931                // Across dialects, a shared spelling needs the include evidence
4932                // that can connect a C declaration to an extern-C definition.
4933                // Within one dialect retain unresolved link-unit candidates;
4934                // their uncertainty is reported by the navigation consumer.
4935                (if declaration.is_callable()
4936                    && reference_uses_c_semantics(self.cpp, file)
4937                        != reference_uses_c_semantics(self.cpp, declaration.source())
4938                {
4939                    crate::identity::cpp_callable_definitions_share_identity_evidence_with_visibility(
4940                        analyzer,
4941                        self,
4942                        candidate,
4943                        declaration,
4944                        |left, right| {
4945                            crate::identity::cpp_header_body_files_are_related(
4946                                self.cpp, self.token, left, right,
4947                            )
4948                        },
4949                    )
4950                } else {
4951                    self.same_logical_callable(analyzer, candidate, declaration)
4952                })
4953                    || flattened_macro_namespace_declaration_matches(
4954                        analyzer,
4955                        self.cpp,
4956                        file,
4957                        candidate,
4958                        declaration,
4959                        reference_byte,
4960                    )
4961            })
4962            .any(|candidate| {
4963                self.physical_declaration_visible_at(
4964                    analyzer,
4965                    file,
4966                    candidate,
4967                    reference_byte,
4968                    &reference_guards,
4969                )
4970            })
4971    }
4972
4973    /// Candidate guard admission, without claiming that the guards hold.
4974    /// Structured inverse paths use this when visibility proof fails, so an
4975    /// unrelated guard preserves uncertainty while a contradiction is excluded.
4976    pub fn declaration_guards_compatible_at_reference(
4977        &self,
4978        analyzer: &CppGraphSource<'_>,
4979        file: &ProjectFile,
4980        declaration: &CodeUnit,
4981        reference: Node<'_>,
4982    ) -> bool {
4983        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4984            return false;
4985        };
4986        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4987        let requirements = declaration_guard_requirements(analyzer, self.cpp, declaration);
4988        // Ordinary unguarded members and references need no translation-unit
4989        // include walk. Build and dialect facts cannot contradict an empty
4990        // source context or an unconditional declaration.
4991        if reference_guards.as_ref().is_some_and(HashSet::is_empty)
4992            && requirements.iter().any(|(_, required)| required.is_empty())
4993        {
4994            return true;
4995        }
4996        let reference_guards = self.effective_reference_guards(file, reference_guards.as_ref());
4997        requirements.iter().any(|(_, required)| {
4998            guards_compatible_at_reference(required, reference_guards.as_ref())
4999        })
5000    }
5001
5002    /// A compatible declaration is only a candidate until its guards follow
5003    /// from the reference's effective context. Keep this proof separate from
5004    /// compatibility so an unknown header or feature guard remains uncertain.
5005    pub fn declaration_guards_implied_at_reference(
5006        &self,
5007        analyzer: &CppGraphSource<'_>,
5008        file: &ProjectFile,
5009        declaration: &CodeUnit,
5010        reference: Node<'_>,
5011    ) -> bool {
5012        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5013            return false;
5014        };
5015        let raw = preprocessor_guard_environment(reference, prepared.source());
5016        let requirements = declaration_guard_requirements(analyzer, self.cpp, declaration);
5017        if raw.is_some() && requirements.iter().any(|(_, required)| required.is_empty()) {
5018            return true;
5019        }
5020        let effective = self.effective_reference_guards(file, raw.as_ref());
5021        requirements.iter().any(|(byte, required)| {
5022            if guard_requirements_hold_at_reference(required, effective.as_ref())
5023                && (declaration.source() != file
5024                    || self.preprocessor_guards_stable_between(
5025                        file,
5026                        (*byte).min(reference.start_byte()),
5027                        (*byte).max(reference.start_byte()),
5028                        required,
5029                    ))
5030            {
5031                return true;
5032            }
5033            if declaration.source() != file {
5034                return false;
5035            }
5036            // A local #define can prove a declaration's branch even when the
5037            // reference lies outside it, and a later #undef cannot remove an
5038            // already-declared member. Evaluate each structured condition at
5039            // its own entry, before events inside its arm change the macros.
5040            let root = prepared.tree().root_node();
5041            let Some(node) = root.descendant_for_byte_range(
5042                *byte,
5043                byte.saturating_add(1).min(prepared.source().len()),
5044            ) else {
5045                return false;
5046            };
5047            let conditionals = owning_preprocessor_conditionals(root, node, prepared.source());
5048            if conditionals.is_empty() {
5049                return false;
5050            }
5051            let mut pending = conditionals.iter().copied().peekable();
5052            let mut ancestor = node.parent();
5053            let mut evaluated = false;
5054            while let Some(conditional) = ancestor {
5055                if pending.peek() == Some(&conditional.start_byte()) {
5056                    pending.next();
5057                    match conditional.kind() {
5058                        "preproc_if" | "preproc_ifdef" | "preproc_elif" => {
5059                            let environment =
5060                                self.macro_environment(file, conditional.start_byte());
5061                            if self.preprocessor_conditional_branch_value(
5062                                conditional,
5063                                node,
5064                                prepared.source(),
5065                                &environment,
5066                            ) != Some(true)
5067                            {
5068                                return false;
5069                            }
5070                            evaluated = true;
5071                        }
5072                        "preproc_else" => {}
5073                        _ => return false,
5074                    }
5075                }
5076                ancestor = conditional.parent();
5077            }
5078            evaluated && pending.peek().is_none()
5079        })
5080    }
5081
5082    /// Whether a physical declaration is visible at an exact structured
5083    /// reference node. Inverse C field references need the reference's own
5084    /// preprocessor environment before using the callable activation path:
5085    /// callable activation can establish that a field declaration is
5086    /// nameable, but it must not admit the opposite branch of that field's
5087    /// conditional family.
5088    pub fn declaration_visible_at_reference(
5089        &self,
5090        analyzer: &CppGraphSource<'_>,
5091        file: &ProjectFile,
5092        declaration: &CodeUnit,
5093        reference: Node<'_>,
5094    ) -> bool {
5095        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5096            return false;
5097        };
5098        if !self.declaration_guards_compatible_at_reference(analyzer, file, declaration, reference)
5099        {
5100            return false;
5101        }
5102        if declaration.source() == file
5103            && !analyzer.reference_uses_c_semantics(file)
5104            && (declaration.is_field() || declaration.is_callable())
5105            && type_owner_of(analyzer, declaration).is_some_and(|owner| {
5106                self.indexed_enclosing_owner_scope(analyzer, file, reference)
5107                    .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
5108            })
5109        {
5110            // C++ complete-class context makes members visible throughout the
5111            // class. The guard check above still excludes a declaration from
5112            // an incompatible preprocessor branch.
5113            return true;
5114        }
5115        if declaration.is_field() && declaration.source() == file {
5116            let reference_byte = reference.start_byte();
5117            let reference_function =
5118                real_function_definition_ancestor(reference, prepared.source());
5119            let guards = OnceCell::new();
5120            let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
5121                self.effective_reference_guards(file, guards)
5122            };
5123            let field_reference = CallableReferenceContext {
5124                effective_guards: &effective_guards,
5125                file_guards: OnceCell::new(),
5126                file,
5127                position: Some(CallableReferencePosition {
5128                    prepared: prepared.as_ref(),
5129                    byte: reference_byte,
5130                    guards: &guards,
5131                }),
5132            };
5133            let mut has_local_declaration = false;
5134            let mut local_declaration_visible = false;
5135            for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
5136            {
5137                let Some(declaration_function) =
5138                    real_function_definition_ancestor(declaration, prepared.source())
5139                else {
5140                    continue;
5141                };
5142                has_local_declaration = true;
5143                if reference_function.is_some_and(|reference_function| {
5144                    reference_function.start_byte() == declaration_function.start_byte()
5145                        && reference_function.end_byte() == declaration_function.end_byte()
5146                }) && callable_preprocessor_context_is_visible_for_reference(
5147                    declaration,
5148                    prepared.source(),
5149                    &field_reference,
5150                ) && declaration.end_byte() < reference_byte
5151                {
5152                    local_declaration_visible = true;
5153                    break;
5154                }
5155            }
5156            if has_local_declaration {
5157                return local_declaration_visible;
5158            }
5159        }
5160        let guards = OnceCell::new();
5161        self.physical_declaration_visible_at(
5162            analyzer,
5163            file,
5164            declaration,
5165            reference.start_byte(),
5166            &guards,
5167        )
5168    }
5169
5170    /// C forward navigation may bind a call to a later same-file definition.
5171    /// There is no earlier source declaration to activate in that legacy C
5172    /// shape, but the call's preprocessor environment must still imply the
5173    /// definition's requirements. Ordinary C++ and inverse visibility retain
5174    /// the declaration-order rule in [`Self::declaration_visible_at`].
5175    pub fn declaration_visible_for_c_forward_call(
5176        &self,
5177        analyzer: &CppGraphSource<'_>,
5178        file: &ProjectFile,
5179        declaration: &CodeUnit,
5180        reference_byte: usize,
5181    ) -> bool {
5182        if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
5183            return true;
5184        }
5185        if declaration.source() != file {
5186            return false;
5187        }
5188        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5189            return false;
5190        };
5191        let reference_guards = prepared
5192            .tree()
5193            .root_node()
5194            .descendant_for_byte_range(reference_byte, reference_byte)
5195            .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
5196        declaration_guard_requirements(analyzer, self.cpp, declaration)
5197            .into_iter()
5198            .any(|(_, required)| {
5199                guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
5200            })
5201    }
5202
5203    pub fn callable_arity_at_reference(
5204        &self,
5205        analyzer: &CppGraphSource<'_>,
5206        file: &ProjectFile,
5207        candidate: &CodeUnit,
5208        reference_byte: usize,
5209    ) -> Option<CallableArity> {
5210        let spec = visibility_memo_value(
5211            &self.callable_reference_specs,
5212            &self.reads,
5213            (file.clone(), logical_symbol_key(candidate)),
5214            || {
5215                let prepared = self.cpp.prepared_syntax(self.token, file)?;
5216                let spec = TargetSpec::from_target(analyzer, candidate)?;
5217                let spec = spec
5218                    .with_visible_callable_arities(
5219                        analyzer,
5220                        self.cpp,
5221                        self,
5222                        file,
5223                        prepared.as_ref(),
5224                    )
5225                    .into_owned();
5226                #[cfg(any(test, feature = "test-support"))]
5227                self.callable_reference_spec_build_count
5228                    .fetch_add(1, Ordering::Relaxed);
5229                Some(Arc::new(spec))
5230            },
5231        );
5232        spec.as_ref()?.callable_arity_at(reference_byte)
5233    }
5234
5235    fn physical_declaration_visible_at(
5236        &self,
5237        analyzer: &CppGraphSource<'_>,
5238        file: &ProjectFile,
5239        declaration: &CodeUnit,
5240        reference_byte: usize,
5241        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
5242    ) -> bool {
5243        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5244            return false;
5245        };
5246        let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
5247            self.effective_reference_guards(file, guards)
5248        };
5249        let reference = CallableReferenceContext {
5250            effective_guards: &effective_guards,
5251            file_guards: OnceCell::new(),
5252            file,
5253            position: Some(CallableReferencePosition {
5254                prepared: prepared.as_ref(),
5255                byte: reference_byte,
5256                guards: reference_guards,
5257            }),
5258        };
5259        if declaration.source() == file {
5260            return callable_declaration_activation_in_file(analyzer, declaration, &reference)
5261                .or_else(|| {
5262                    self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
5263                })
5264                .is_some_and(|activation| activation < reference_byte);
5265        }
5266        if self
5267            .exhaustive_guard_family_activation(analyzer, declaration, &reference)
5268            .is_some()
5269        {
5270            // Structured coverage proves this logical declaration exists in
5271            // every arm. The include path still needs its own proof.
5272            return self.foreign_declaration_reachable_at_reference(
5273                file,
5274                prepared.as_ref(),
5275                declaration.source(),
5276                &HashSet::default(),
5277                reference.guards(),
5278                reference_byte,
5279            );
5280        }
5281        if !self.foreign_callable_declaration_is_active(analyzer, declaration, &reference) {
5282            return false;
5283        }
5284        declaration_guard_requirements(analyzer, self.cpp, declaration)
5285            .into_iter()
5286            .any(|(_, declaration_guards)| {
5287                self.foreign_declaration_reachable_at_reference(
5288                    file,
5289                    prepared.as_ref(),
5290                    declaration.source(),
5291                    &declaration_guards,
5292                    reference.guards(),
5293                    reference_byte,
5294                )
5295            })
5296    }
5297
5298    /// Whether the donor's callable facts admit this candidate. Build coverage
5299    /// selects required macro facts; without it, compatibility permits discovery.
5300    /// `physical_declaration_visible_at` separately requires implication and a
5301    /// proven include path before publishing an exact answer (#6902).
5302    fn foreign_callable_declaration_is_active(
5303        &self,
5304        analyzer: &CppGraphSource<'_>,
5305        declaration: &CodeUnit,
5306        reference: &CallableReferenceContext<'_>,
5307    ) -> bool {
5308        let build_decides = !self.compile_context_is_absent(reference.file);
5309        let active = reference.guards();
5310        analyzer
5311            .declaration_source_occurrences(declaration)
5312            .into_iter()
5313            .flatten()
5314            .any(|occurrence| {
5315                if occurrence.fact.callable_activation.is_none() {
5316                    return false;
5317                }
5318                let Some(required) = occurrence
5319                    .fact
5320                    .callable_guards
5321                    .as_ref()
5322                    .and_then(cpp_guard_set_to_runtime)
5323                else {
5324                    return false;
5325                };
5326                required.is_empty()
5327                    || if build_decides {
5328                        guard_requirements_hold_at_reference(&required, active)
5329                    } else {
5330                        guards_compatible_at_reference(&required, active)
5331                    }
5332            })
5333    }
5334
5335    pub fn external_type_candidate_visible_at(
5336        &self,
5337        file: &ProjectFile,
5338        candidate: &CodeUnit,
5339        reference_byte: usize,
5340    ) -> bool {
5341        if candidate.source() == file {
5342            return true;
5343        }
5344        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5345            return false;
5346        };
5347        self.visible_identifier_candidates(file, candidate.identifier())
5348            .filter(|peer| same_logical_symbol(candidate, peer))
5349            .any(|peer| {
5350                peer.source() == file
5351                    || self
5352                        .include_activation_for_source(
5353                            self.cpp,
5354                            file,
5355                            prepared.as_ref(),
5356                            peer.source(),
5357                        )
5358                        .is_some_and(|activation| activation <= reference_byte)
5359            })
5360    }
5361
5362    pub fn external_type_declaration_visible_at(
5363        &self,
5364        file: &ProjectFile,
5365        candidate: &CodeUnit,
5366        reference_byte: usize,
5367    ) -> bool {
5368        if candidate.source() == file {
5369            return true;
5370        }
5371        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5372            return false;
5373        };
5374        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5375            .is_some_and(|activation| activation <= reference_byte)
5376    }
5377
5378    /// The preprocessor facts the build proves for a reference sited in
5379    /// `file` (#2011).
5380    ///
5381    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
5382    /// `Defined` fact, and a fact holds only when every compile configuration
5383    /// that governs the file agrees on it (intersection). The facts are
5384    /// strictly additive to the reference's active guard set: they can prove a
5385    /// required guard, but the guard check itself is never weakened and no
5386    /// implication is ever inferred from source text.
5387    ///
5388    /// A file with its own database entry answers from that entry alone
5389    /// (phase 1). A header takes its context from the translation units whose
5390    /// include closure reaches it, intersected across all of them (phase 2):
5391    /// the header is compiled once per including TU, so a fact holds for a
5392    /// header-sited reference only when every one of those compilations
5393    /// proves it. A reaching TU the database does not cover proves nothing,
5394    /// which empties the intersection. A file nothing covers or reaches has
5395    /// no facts and every check runs on source structure alone.
5396    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5397        visibility_memo_value(
5398            &self.compile_proven_guard_cells,
5399            &self.reads,
5400            file.clone(),
5401            || Arc::new(compile_proven_guard_facts(self.cpp, file)),
5402        )
5403    }
5404
5405    /// The rule a conditional `#include` path's guards face on the way to a
5406    /// reference in `file`.
5407    ///
5408    /// This is the include-edge analogue of the choice
5409    /// [`Self::foreign_callable_declaration_is_active`] makes for a foreign
5410    /// declaration's own guards (#2988), and it is decided by the same thing:
5411    /// what settles the reference's configuration. Memoized per file because
5412    /// the answer depends on nothing else, and the callers ask it once per
5413    /// candidate.
5414    fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5415        visibility_memo_value(
5416            &self.include_path_admission_cells,
5417            &self.reads,
5418            file.clone(),
5419            || {
5420                if self.compile_context_is_absent(file) {
5421                    IncludePathAdmission::Compatible
5422                } else {
5423                    IncludePathAdmission::Proven
5424                }
5425            },
5426        )
5427    }
5428
5429    /// Whether no compile data covers the compilations of `file`: it has no
5430    /// database entry of its own, and either nothing reaches it or some
5431    /// translation unit that reaches it has no entry. This is the state a
5432    /// regenerated `compile_commands.json` could decide; data that is present
5433    /// for every governing compilation but does not prove a guard is a
5434    /// decided conservative miss, not this state.
5435    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5436        if !self.cpp.compile_contexts_for(file).is_empty() {
5437            return false;
5438        }
5439        let translation_units = self.cpp.reaching_translation_units(file);
5440        translation_units.is_empty()
5441            || translation_units
5442                .iter()
5443                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5444    }
5445
5446    /// Whether a lookup miss for `identifier` in `file` is explainable by
5447    /// missing compile context (#2011): some same-name declaration is
5448    /// reachable through a conditional include whose required guards neither
5449    /// contradict the reference's active guards nor follow from them, and the
5450    /// translation unit has no compile-commands entry that could decide the
5451    /// question. Callers surface this as an explicit "requires compile
5452    /// context" incompleteness instead of an indistinguishable miss.
5453    ///
5454    /// A structurally disproven declaration (contradicting guards) and a TU
5455    /// whose compile context exists but does not prove the guard both answer
5456    /// `false`: those misses are decided, not incomplete.
5457    pub fn miss_requires_compile_context(
5458        &self,
5459        file: &ProjectFile,
5460        identifier: &str,
5461        reference: Node<'_>,
5462    ) -> bool {
5463        if !self.compile_context_is_absent(file) {
5464            return false;
5465        }
5466        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5467            return false;
5468        };
5469        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5470        let reference_byte = reference.start_byte();
5471        let mut sources = self
5472            .visible_identifier_candidates(file, identifier)
5473            .map(CodeUnit::source)
5474            .filter(|source| *source != file)
5475            .collect::<Vec<_>>();
5476        sources.sort();
5477        sources.dedup();
5478        sources.into_iter().any(|declaration_source| {
5479            self.conditional_include_projections_for_source(
5480                file,
5481                prepared.as_ref(),
5482                declaration_source,
5483            )
5484            .iter()
5485            .any(|projection| {
5486                projection.activation_byte <= reference_byte
5487                    && !guard_requirements_hold_at_reference(
5488                        &projection.required_guards,
5489                        reference_guards.as_ref(),
5490                    )
5491                    && guards_compatible_at_reference(
5492                        &projection.required_guards,
5493                        reference_guards.as_ref(),
5494                    )
5495            })
5496        })
5497    }
5498
5499    /// Supply this query's cached build facts to the shared guard constructor.
5500    fn effective_reference_guards(
5501        &self,
5502        file: &ProjectFile,
5503        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5504    ) -> Option<HashSet<PreprocessorGuard>> {
5505        effective_reference_guards(
5506            self.cpp,
5507            file,
5508            reference_guards,
5509            &self.compile_proven_guards(file),
5510            &|| self.include_language_guard_is_stable(file),
5511        )
5512    }
5513
5514    /// Decide whether a declaration that lives in another file reaches a
5515    /// reference in `file`.
5516    ///
5517    /// Candidate discovery admits non-contradicting guards. Proven visibility
5518    /// additionally requires the declaration's guards to follow from the
5519    /// effective reference context; independent opaque conditions imply nothing
5520    /// about one another (#6902).
5521    fn foreign_declaration_reachable_at_reference(
5522        &self,
5523        file: &ProjectFile,
5524        prepared: &PreparedSyntaxTree,
5525        declaration_source: &ProjectFile,
5526        declaration_guards: &HashSet<PreprocessorGuard>,
5527        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5528        reference_byte: usize,
5529    ) -> bool {
5530        let effective = self.effective_reference_guards(file, reference_guards);
5531        let reference_guards = effective.as_ref();
5532        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5533            eprintln!(
5534                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5535                declaration_source.rel_path().display(),
5536            );
5537        }
5538        if !guard_requirements_hold_at_reference(declaration_guards, reference_guards) {
5539            return false;
5540        }
5541        if self
5542            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5543            .is_some_and(|activation| activation <= reference_byte)
5544        {
5545            return true;
5546        }
5547        let projections =
5548            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5549        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5550            eprintln!(
5551                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5552                declaration_source.rel_path().display(),
5553                declaration_guards.len(),
5554                self.compile_proven_guards(file).len(),
5555                projections.len(),
5556            );
5557        }
5558        let admission = IncludePathAdmission::Proven;
5559        projections.iter().any(|projection| {
5560            projection.activation_byte <= reference_byte
5561                && admission.admits(
5562                    &projection.required_guards,
5563                    &projection.partial_guards,
5564                    reference_guards,
5565                )
5566                && self.preprocessor_guards_stable_between(
5567                    file,
5568                    projection.activation_byte,
5569                    reference_byte,
5570                    &projection.required_guards,
5571                )
5572        })
5573    }
5574
5575    fn foreign_declaration_may_be_reachable_from_raw_guards(
5576        &self,
5577        file: &ProjectFile,
5578        prepared: &PreparedSyntaxTree,
5579        declaration_source: &ProjectFile,
5580        declaration_guards: &HashSet<PreprocessorGuard>,
5581        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5582        reference_byte: usize,
5583    ) -> bool {
5584        let proven = self.compile_proven_guards(file);
5585        let augmented;
5586        let reference_guards = match reference_guards {
5587            Some(active) if !proven.is_empty() => {
5588                augmented = active.union(&proven).cloned().collect();
5589                Some(&augmented)
5590            }
5591            other => other,
5592        };
5593        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5594            return false;
5595        }
5596        if self
5597            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5598            .is_some_and(|activation| activation <= reference_byte)
5599        {
5600            return true;
5601        }
5602        let effective = OnceCell::new();
5603        let reachable = find_conditional_include_projection_for_source(
5604            self.cpp,
5605            self.token,
5606            file,
5607            prepared,
5608            declaration_source,
5609            self.include_path_admission(file),
5610            reference_guards,
5611            reference_byte,
5612            &|| {
5613                effective
5614                    .get_or_init(|| {
5615                        self.effective_reference_guards(file, Some(&HashSet::default()))
5616                    })
5617                    .as_ref()
5618            },
5619            &|| {
5620                #[cfg(any(test, feature = "test-support"))]
5621                self.conditional_include_target_state_count
5622                    .fetch_add(1, Ordering::Relaxed);
5623            },
5624        );
5625        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5626            eprintln!(
5627                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5628                declaration_source.rel_path().display(),
5629                declaration_guards.len(),
5630                proven.len(),
5631                reference_guards.map_or(0, HashSet::len),
5632            );
5633        }
5634        reachable
5635    }
5636
5637    fn foreign_declaration_reachable_from_compile_proven_guards(
5638        &self,
5639        file: &ProjectFile,
5640        prepared: &PreparedSyntaxTree,
5641        declaration_source: &ProjectFile,
5642        declaration_guards: &HashSet<PreprocessorGuard>,
5643        reference_byte: usize,
5644    ) -> bool {
5645        let proven = self.compile_proven_guards(file);
5646        if proven.is_empty()
5647            || !guard_requirements_hold_at_reference(declaration_guards, Some(proven.as_ref()))
5648        {
5649            return false;
5650        }
5651        let projections =
5652            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5653        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5654            eprintln!(
5655                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5656                declaration_source.rel_path().display(),
5657                declaration_guards.len(),
5658                proven.len(),
5659                projections.len(),
5660            );
5661        }
5662        projections.iter().any(|projection| {
5663            projection.activation_byte <= reference_byte
5664                    && guard_requirements_hold_at_reference(
5665                        &projection.required_guards,
5666                        Some(proven.as_ref()),
5667                    )
5668                    // Build facts hold at translation-unit entry. A source
5669                    // `#undef` or an earlier include may invalidate one before
5670                    // this conditional include is reached; mutations after the
5671                    // include cannot revoke declarations it already supplied.
5672                    && self.preprocessor_guards_stable_between(
5673                        file,
5674                        0,
5675                        projection.activation_byte,
5676                        &projection.required_guards,
5677                    )
5678        })
5679    }
5680
5681    /// Whether one declaration site of `site_source` introduces its name before
5682    /// `reference_byte` of `file` without consulting the reference's own guard
5683    /// environment: an unguarded site earlier in the file or reached through
5684    /// an unconditional include, or a foreign site the build's proven defines
5685    /// reach.
5686    fn declaration_site_visible_without_reference_environment(
5687        &self,
5688        file: &ProjectFile,
5689        prepared: &PreparedSyntaxTree,
5690        site_source: &ProjectFile,
5691        site_byte: usize,
5692        site_guards: &HashSet<PreprocessorGuard>,
5693        reference_byte: usize,
5694    ) -> bool {
5695        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5696        if site_source == file {
5697            let visible = site_byte < reference_byte && site_guards.is_empty();
5698            if report_stats {
5699                eprintln!(
5700                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5701                    site_source.rel_path().display(),
5702                    site_guards.len(),
5703                );
5704            }
5705            return visible;
5706        }
5707        let direct = site_guards.is_empty()
5708            && self
5709                .include_activation_for_source(self.cpp, file, prepared, site_source)
5710                .is_some_and(|activation| activation <= reference_byte);
5711        let compile_proven = !direct
5712            && self.foreign_declaration_reachable_from_compile_proven_guards(
5713                file,
5714                prepared,
5715                site_source,
5716                site_guards,
5717                reference_byte,
5718            );
5719        if report_stats {
5720            eprintln!(
5721                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5722                site_source.rel_path().display(),
5723                site_guards.len(),
5724            );
5725        }
5726        direct || compile_proven
5727    }
5728
5729    /// Whether one declaration site of `site_source` is in effect at
5730    /// `reference_byte` of `file` under the reference's guard environment:
5731    /// `raw_reference_guards` as written, and `reference_guards_at_site` after
5732    /// the macro events before the reference.
5733    #[allow(clippy::too_many_arguments)]
5734    fn declaration_site_visible_at_reference<'g>(
5735        &self,
5736        file: &ProjectFile,
5737        prepared: &PreparedSyntaxTree,
5738        site_source: &ProjectFile,
5739        site: &DeclarationGuardSite,
5740        reference_byte: usize,
5741        raw_reference_guards: Option<&HashSet<PreprocessorGuard>>,
5742        reference_guards_at_site: impl Fn() -> Option<&'g HashSet<PreprocessorGuard>>,
5743    ) -> bool {
5744        if site_source == file {
5745            if site.byte >= reference_byte {
5746                return false;
5747            }
5748            // The reference's own conditional context must either prove the
5749            // declaration's requirements, or follow a completed family that
5750            // encloses the declaration (#3297); either way it must not
5751            // contradict them.
5752            let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5753                guard_requirements_hold_at_reference(&site.guards, guards)
5754                    || (site.completed_family_before_reference
5755                        && guards_compatible_at_reference(&site.guards, guards))
5756            };
5757            let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
5758                self.effective_reference_guards(file, guards)
5759            };
5760            let raw_reference_guards = effective_guards(raw_reference_guards);
5761            if !active(raw_reference_guards.as_ref()) {
5762                return false;
5763            }
5764            let reference_guards_at_site = effective_guards(reference_guards_at_site());
5765            return active(reference_guards_at_site.as_ref())
5766                && self.preprocessor_guards_stable_between(
5767                    file,
5768                    site.byte,
5769                    reference_byte,
5770                    &site.guards,
5771                );
5772        }
5773        let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5774            file,
5775            prepared,
5776            site_source,
5777            &site.guards,
5778            raw_reference_guards,
5779            reference_byte,
5780        );
5781        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5782            eprintln!(
5783                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5784                site_source.rel_path().display(),
5785                site.guards.len(),
5786            );
5787        }
5788        raw_feasible
5789            && self.foreign_declaration_reachable_at_reference(
5790                file,
5791                prepared,
5792                site_source,
5793                &site.guards,
5794                reference_guards_at_site(),
5795                reference_byte,
5796            )
5797    }
5798
5799    /// Whether a scope-level binding written at `site_byte` of `site_source`
5800    /// under `site_guards` is in effect at `reference`, by the same site rule
5801    /// [`Self::external_type_candidate_visible_in_context`] applies to each
5802    /// declaration range.
5803    pub fn binding_site_visible_at_reference(
5804        &self,
5805        file: &ProjectFile,
5806        site_source: &ProjectFile,
5807        site_byte: usize,
5808        site_guards: &HashSet<PreprocessorGuard>,
5809        reference: Node<'_>,
5810    ) -> bool {
5811        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5812            return false;
5813        };
5814        if self.declaration_site_visible_without_reference_environment(
5815            file,
5816            prepared.as_ref(),
5817            site_source,
5818            site_byte,
5819            site_guards,
5820            reference.start_byte(),
5821        ) {
5822            return true;
5823        }
5824        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5825        let reference_guards = OnceCell::new();
5826        self.declaration_site_visible_at_reference(
5827            file,
5828            prepared.as_ref(),
5829            site_source,
5830            &DeclarationGuardSite {
5831                byte: site_byte,
5832                guards: site_guards.clone(),
5833                completed_family_before_reference: false,
5834            },
5835            reference.start_byte(),
5836            raw_reference_guards.as_ref(),
5837            || {
5838                reference_guards
5839                    .get_or_init(|| {
5840                        let environment = self.macro_environment(file, reference.start_byte());
5841                        raw_reference_guards
5842                            .clone()
5843                            .filter(|guards| environment.guard_requirements_may_hold(guards))
5844                    })
5845                    .as_ref()
5846            },
5847        )
5848    }
5849
5850    pub fn external_type_candidate_visible_in_context(
5851        &self,
5852        analyzer: &CppGraphSource<'_>,
5853        file: &ProjectFile,
5854        candidate: &CodeUnit,
5855        reference: Node<'_>,
5856    ) -> bool {
5857        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5858        if report_stats {
5859            eprintln!(
5860                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5861                candidate.fq_name(),
5862                candidate.source().rel_path().display(),
5863                file.rel_path().display(),
5864                reference.start_byte(),
5865            );
5866        }
5867        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5868            return false;
5869        };
5870        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5871        let reference_guards = OnceCell::new();
5872        let reference_guards_at_site = || {
5873            reference_guards.get_or_init(|| {
5874                let started = Instant::now();
5875                if report_stats {
5876                    eprintln!(
5877                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5878                        file.rel_path().display(),
5879                        reference.start_byte(),
5880                        raw_reference_guards.as_ref().map_or(0, HashSet::len),
5881                    );
5882                }
5883                let macro_environment = self.macro_environment(file, reference.start_byte());
5884                let filtered = raw_reference_guards
5885                    .clone()
5886                    .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5887                if report_stats {
5888                    eprintln!(
5889                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5890                        filtered.is_some(),
5891                        started.elapsed().as_millis(),
5892                    );
5893                }
5894                filtered
5895            })
5896        };
5897
5898        let peers = self
5899            .visible_identifier_candidates(file, candidate.identifier())
5900            .filter(|peer| same_logical_symbol(candidate, peer))
5901            .collect::<Vec<_>>();
5902        if report_stats {
5903            let peer_sources = peers
5904                .iter()
5905                .map(|peer| peer.source().rel_path().display().to_string())
5906                .collect::<Vec<_>>();
5907            eprintln!(
5908                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5909                candidate.fq_name(),
5910            );
5911        }
5912        let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5913            declaration_guard_requirements(analyzer, self.cpp, peer)
5914                .into_iter()
5915                .any(|(declaration_byte, declaration_guards)| {
5916                    self.declaration_site_visible_without_reference_environment(
5917                        file,
5918                        prepared.as_ref(),
5919                        peer.source(),
5920                        declaration_byte,
5921                        &declaration_guards,
5922                        reference.start_byte(),
5923                    )
5924                })
5925        });
5926        if directly_visible_without_reference_environment {
5927            if report_stats {
5928                eprintln!(
5929                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5930                    candidate.fq_name(),
5931                );
5932            }
5933            return true;
5934        }
5935        let directly_visible = peers.iter().any(|peer| {
5936            declaration_guard_sites(analyzer, self.cpp, peer, reference.start_byte())
5937                .into_iter()
5938                .any(|site| {
5939                    self.declaration_site_visible_at_reference(
5940                        file,
5941                        prepared.as_ref(),
5942                        peer.source(),
5943                        &site,
5944                        reference.start_byte(),
5945                        raw_reference_guards.as_ref(),
5946                        || reference_guards_at_site().as_ref(),
5947                    )
5948                })
5949        });
5950        if directly_visible {
5951            if report_stats {
5952                eprintln!(
5953                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5954                    candidate.fq_name(),
5955                );
5956            }
5957            return true;
5958        }
5959        let complementary = self
5960            .visible_identifier_candidates(file, candidate.identifier())
5961            .filter(|peer| {
5962                peer.kind() == candidate.kind()
5963                    && peer.fq_name() == candidate.fq_name()
5964                    && peer.source() == candidate.source()
5965            })
5966            .collect::<Vec<_>>();
5967        // A completed #if/#else family declares the shared source-level name
5968        // before this reference. A later macro mutation cannot revoke that
5969        // declaration. The family gate below rejects declarations split across
5970        // separate conditional blocks, where mutation can change coverage.
5971        let complementary_family =
5972            self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5973        let raw_candidate_branch_compatible = complementary_family
5974            && raw_reference_guards.as_ref().is_some_and(|active| {
5975                declaration_guard_requirements(analyzer, self.cpp, candidate)
5976                    .iter()
5977                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5978            });
5979        if report_stats {
5980            eprintln!(
5981                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5982                candidate.fq_name(),
5983                complementary.len(),
5984                complementary_family,
5985                raw_candidate_branch_compatible,
5986            );
5987        }
5988        let candidate_branch_compatible = raw_candidate_branch_compatible
5989            && reference_guards_at_site().as_ref().is_some_and(|active| {
5990                declaration_guard_requirements(analyzer, self.cpp, candidate)
5991                    .iter()
5992                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5993            });
5994        let complementary_visible = candidate_branch_compatible
5995            && if candidate.source() == file {
5996                declaration_guard_requirements(analyzer, self.cpp, candidate)
5997                    .iter()
5998                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5999            } else {
6000                self.include_activation_for_source(
6001                    self.cpp,
6002                    file,
6003                    prepared.as_ref(),
6004                    candidate.source(),
6005                )
6006                .is_some_and(|activation| activation <= reference.start_byte())
6007            };
6008        if report_stats {
6009            eprintln!(
6010                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
6011                if complementary_visible {
6012                    "complementary"
6013                } else {
6014                    "missing"
6015                },
6016                candidate.fq_name(),
6017            );
6018        }
6019        complementary_visible
6020    }
6021
6022    pub fn is_exhaustive_same_fqn_type_declaration_family(
6023        &self,
6024        analyzer: &CppGraphSource<'_>,
6025        file: &ProjectFile,
6026        candidate: &CodeUnit,
6027    ) -> bool {
6028        let candidates = self
6029            .visible_identifier_candidates(file, candidate.identifier())
6030            .filter(|peer| {
6031                peer.kind() == candidate.kind()
6032                    && peer.fq_name() == candidate.fq_name()
6033                    && peer.source() == candidate.source()
6034            })
6035            .collect::<Vec<_>>();
6036        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
6037    }
6038
6039    /// Prove a nested type alias used as a dependent member-pointer owner when
6040    /// its owning class has mutually-exclusive declarations.  A common C++11
6041    /// compatibility shape provides the owning class in one preprocessor
6042    /// branch and aliases it to a standard-library type in the other branch;
6043    /// the nested fallback alias is therefore not itself active in every
6044    /// branch even though the qualified owner API is.
6045    ///
6046    /// This is deliberately narrower than ordinary type visibility.  The
6047    /// caller has already recovered a member-pointer owner path from the CST;
6048    /// this helper additionally requires the target's structured parent to
6049    /// match that path, physical source visibility, and exact preprocessor
6050    /// guard agreement with the parent declaration.  Only then may the
6051    /// parent's direct/complementary same-FQN visibility stand in for the
6052    /// nested terminal's active-branch check.
6053    pub fn dependent_member_pointer_alias_visible_in_context(
6054        &self,
6055        analyzer: &CppGraphSource<'_>,
6056        file: &ProjectFile,
6057        candidate: &CodeUnit,
6058        owner_components: &[String],
6059        reference: Node<'_>,
6060    ) -> bool {
6061        if !analyzer
6062            .type_alias_provider()
6063            .is_some_and(|provider| provider.is_type_alias(candidate))
6064        {
6065            return false;
6066        }
6067        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
6068            return false;
6069        };
6070        if terminal != candidate.identifier()
6071            || canonical_cpp_scope_components(candidate) != owner_components
6072        {
6073            return false;
6074        }
6075        let Some(expected_parent_fq_name) =
6076            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
6077        else {
6078            return false;
6079        };
6080        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
6081            return false;
6082        };
6083        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
6084            || parent_anchor.source() != candidate.source()
6085            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
6086        {
6087            return false;
6088        }
6089
6090        // The ordinary path already handles unguarded aliases (and preserves
6091        // same-file declaration ordering).  This fallback is only for a
6092        // physically visible declaration whose guard is the owning branch's
6093        // guard, so reject a same-file declaration that appears after the
6094        // reference before considering guard compatibility.
6095        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
6096            || candidate.source() == file
6097                && !analyzer
6098                    .ranges(candidate)
6099                    .iter()
6100                    .any(|range| range.start_byte < reference.start_byte())
6101        {
6102            return false;
6103        }
6104
6105        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
6106        if candidate_guards.is_empty() {
6107            return false;
6108        }
6109        let same_guard_sets =
6110            |left: &[(usize, HashSet<PreprocessorGuard>)],
6111             right: &[(usize, HashSet<PreprocessorGuard>)]| {
6112                left.iter().all(|(_, left_guards)| {
6113                    right
6114                        .iter()
6115                        .any(|(_, right_guards)| left_guards == right_guards)
6116                })
6117            };
6118        let parent_candidates = self
6119            .visible_identifier_candidates(file, parent_anchor.identifier())
6120            .filter(|peer| {
6121                peer.kind() == parent_anchor.kind()
6122                    && peer.fq_name() == expected_parent_fq_name.as_str()
6123                    && peer.source() == parent_anchor.source()
6124                    && canonical_cpp_scope_components(peer) == owner_prefix
6125            })
6126            .filter_map(|peer| {
6127                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
6128                (candidate_guards.len() == parent_guards.len()
6129                    && same_guard_sets(&candidate_guards, &parent_guards)
6130                    && same_guard_sets(&parent_guards, &candidate_guards))
6131                .then(|| (peer.clone(), parent_guards))
6132            })
6133            .collect::<Vec<_>>();
6134        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
6135            return false;
6136        };
6137
6138        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6139            return false;
6140        };
6141        let raw = preprocessor_guard_environment(reference, prepared.source());
6142        let Some(reference_guards) = self.effective_reference_guards(file, raw.as_ref()) else {
6143            return false;
6144        };
6145        // An external header selects its declaration branch before the
6146        // reference file is parsed. Require compatible reference guards, but
6147        // do not test the header's guard expression for stability in the
6148        // reference file. Same-file aliases still require that stability.
6149        if !candidate_guards.iter().any(|(_, target_guards)| {
6150            guards_compatible_at_reference(target_guards, Some(&reference_guards))
6151                && (candidate.source() != file
6152                    || self.preprocessor_guards_stable_between(
6153                        file,
6154                        0,
6155                        reference.start_byte(),
6156                        target_guards,
6157                    ))
6158        }) {
6159            return false;
6160        }
6161
6162        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
6163    }
6164
6165    /// Check a type candidate's preprocessor/import context without imposing
6166    /// ordinary declaration-before-reference ordering for same-file peers.
6167    ///
6168    /// C++ class scope makes member names visible throughout the complete
6169    /// class, including a trailing return type that appears before the member
6170    /// alias declaration in source order. Callers must first prove that the
6171    /// reference is inside the candidate's indexed class owner; this helper
6172    /// only relaxes the byte-order predicate while retaining guard and include
6173    /// activation checks.
6174    pub fn external_type_candidate_guard_compatible_in_context(
6175        &self,
6176        analyzer: &CppGraphSource<'_>,
6177        file: &ProjectFile,
6178        candidate: &CodeUnit,
6179        reference: Node<'_>,
6180    ) -> bool {
6181        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6182            return false;
6183        };
6184        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
6185
6186        self.visible_identifier_candidates(file, candidate.identifier())
6187            .filter(|peer| same_logical_symbol(candidate, peer))
6188            .any(|peer| {
6189                declaration_guard_requirements(analyzer, self.cpp, peer)
6190                    .into_iter()
6191                    .any(|(declaration_byte, declaration_guards)| {
6192                        if peer.source() == file {
6193                            let (start, end) = if declaration_byte <= reference.start_byte() {
6194                                (declaration_byte, reference.start_byte())
6195                            } else {
6196                                (reference.start_byte(), declaration_byte)
6197                            };
6198                            return guard_requirements_hold_at_reference(
6199                                &declaration_guards,
6200                                reference_guards.as_ref(),
6201                            ) && self.preprocessor_guards_stable_between(
6202                                file,
6203                                start,
6204                                end,
6205                                &declaration_guards,
6206                            );
6207                        }
6208                        self.foreign_declaration_reachable_at_reference(
6209                            file,
6210                            prepared.as_ref(),
6211                            peer.source(),
6212                            &declaration_guards,
6213                            reference_guards.as_ref(),
6214                            reference.start_byte(),
6215                        )
6216                    })
6217            })
6218    }
6219
6220    /// Whether a same-file callable declaration is nameable from `reference`
6221    /// after deliberately relaxing declaration-before-reference ordering.
6222    ///
6223    /// Ordinary lookup still requires an earlier declaration. Definition
6224    /// navigation for incomplete C translation units may recover a later
6225    /// definition, but only when it is at file scope and its preprocessor
6226    /// requirements hold at the call (#2404).
6227    pub fn same_file_callable_guard_compatible_ignoring_order(
6228        &self,
6229        analyzer: &CppGraphSource<'_>,
6230        file: &ProjectFile,
6231        candidate: &CodeUnit,
6232        reference: Node<'_>,
6233    ) -> bool {
6234        if candidate.source() != file || !candidate.is_callable() {
6235            return false;
6236        }
6237        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6238            return false;
6239        };
6240        let guards = OnceCell::new();
6241        let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
6242            self.effective_reference_guards(file, guards)
6243        };
6244        let context = CallableReferenceContext {
6245            effective_guards: &effective_guards,
6246            file_guards: OnceCell::new(),
6247            file,
6248            position: Some(CallableReferencePosition {
6249                prepared: prepared.as_ref(),
6250                byte: reference.start_byte(),
6251                guards: &guards,
6252            }),
6253        };
6254        analyzer
6255            .declaration_source_occurrences(candidate)
6256            .into_iter()
6257            .flatten()
6258            .filter_map(|occurrence| {
6259                let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
6260                Some(callable_guard_requirements_are_visible(
6261                    &guards,
6262                    occurrence.fact.callable_guard_completion_byte,
6263                    &context,
6264                ))
6265            })
6266            .any(|visible| visible)
6267    }
6268
6269    pub fn type_candidate_may_be_visible_before_reference(
6270        &self,
6271        analyzer: &CppGraphSource<'_>,
6272        file: &ProjectFile,
6273        candidate: &CodeUnit,
6274        reference_byte: usize,
6275    ) -> bool {
6276        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6277            return false;
6278        };
6279        let root = prepared.tree().root_node();
6280        let end_byte = reference_byte
6281            .saturating_add(1)
6282            .min(prepared.source().len());
6283        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
6284            return false;
6285        };
6286        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
6287    }
6288
6289    pub fn preprocessor_guards_stable_between(
6290        &self,
6291        file: &ProjectFile,
6292        start_byte: usize,
6293        end_byte: usize,
6294        guards: &HashSet<PreprocessorGuard>,
6295    ) -> bool {
6296        if guards.is_empty() || start_byte >= end_byte {
6297            return true;
6298        }
6299        let cell = self.macro_event_cell(file);
6300        let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
6301        let mut visited = HashSet::from_iter([file.clone()]);
6302        !events.iter().any(|event| {
6303            event.byte() >= start_byte
6304                && event.byte() < end_byte
6305                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
6306        })
6307    }
6308
6309    fn macro_event_may_mutate_guards(
6310        &self,
6311        event: &MacroEvent,
6312        guards: &HashSet<PreprocessorGuard>,
6313        visited: &mut HashSet<ProjectFile>,
6314    ) -> bool {
6315        match event {
6316            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
6317                guards.iter().any(|guard| guard.may_depend_on_macro(name))
6318            }
6319            MacroEvent::Include { targets, .. } => {
6320                targets.is_empty()
6321                    || targets
6322                        .iter()
6323                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
6324            }
6325            MacroEvent::Invalidate { .. } => true,
6326        }
6327    }
6328
6329    fn source_may_mutate_guards(
6330        &self,
6331        file: &ProjectFile,
6332        guards: &HashSet<PreprocessorGuard>,
6333        visited: &mut HashSet<ProjectFile>,
6334    ) -> bool {
6335        if !visited.insert(file.clone()) {
6336            return false;
6337        }
6338        let cell = self.macro_event_cell(file);
6339        let events = cell.get_or_init(|| Arc::from(self.collect_macro_events(file)));
6340        events
6341            .iter()
6342            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
6343    }
6344
6345    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
6346        let normalized = normalize_reference_name(raw_name)?;
6347        self.type_candidates(file, &normalized)
6348            .into_iter()
6349            .next()
6350            .cloned()
6351    }
6352
6353    /// Mirror forward navigation's visible-name fallback for a bare parameter
6354    /// type after lexical owner and inheritance lookup is exhausted.
6355    ///
6356    /// Generated or otherwise unindexed base classes can hide the alias that
6357    /// makes a parameter type valid C++. Accept the fallback only when every
6358    /// include-visible class or alias with that spelling canonicalizes to one
6359    /// logical type. A shadowing local type resolves lexically before this
6360    /// path, while distinct visible types keep the result ambiguous.
6361    pub fn unique_visible_parameter_type_fallback(
6362        &self,
6363        analyzer: &CppGraphSource<'_>,
6364        file: &ProjectFile,
6365        node: Node<'_>,
6366        source: &str,
6367    ) -> Option<CodeUnit> {
6368        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
6369            return None;
6370        }
6371        let name = node_text(node, source);
6372        let candidates = self
6373            .visible_identifier_candidates(file, name)
6374            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
6375            .filter(|candidate| {
6376                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
6377            })
6378            .collect::<Vec<_>>();
6379        self.unique_canonical_type_candidate(analyzer, file, &candidates)
6380    }
6381
6382    pub fn resolve_type_node_result(
6383        &self,
6384        file: &ProjectFile,
6385        node: Node<'_>,
6386        source: &str,
6387    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6388        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6389            return Ok(None);
6390        };
6391        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6392            return Ok(Some(primary));
6393        };
6394        self.resolve_template_arguments(file, primary, &arguments)
6395            .map(Some)
6396    }
6397
6398    pub fn resolve_type_node_primary(
6399        &self,
6400        file: &ProjectFile,
6401        node: Node<'_>,
6402        source: &str,
6403    ) -> Option<CodeUnit> {
6404        let components = cpp_type_name_components(node, source)?;
6405        if !is_globally_qualified_cpp_name(node)
6406            && let Some(resolution) =
6407                function_local_type_resolution(&self.cpp_source(), file, node, &components, source)
6408        {
6409            return match resolution {
6410                LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
6411                LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
6412            };
6413        }
6414        self.resolve_type(file, &components.join("::"))
6415    }
6416
6417    pub fn resolve_template_arguments(
6418        &self,
6419        file: &ProjectFile,
6420        primary: CodeUnit,
6421        arguments: &[CppTemplateExpression],
6422    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6423        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6424    }
6425
6426    fn resolve_template_arguments_inner(
6427        &self,
6428        file: &ProjectFile,
6429        primary: CodeUnit,
6430        arguments: &[CppTemplateExpression],
6431        seen_aliases: &mut HashSet<CodeUnit>,
6432    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6433        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6434            && let Some(alias_target) = &metadata.alias_target
6435        {
6436            if !seen_aliases.insert(primary.clone()) {
6437                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6438            }
6439            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6440                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6441            let target_name = alias_target.components.join("::");
6442            let target_primary = if alias_target.global {
6443                unique_logical_type_candidate(self.type_candidates(file, &target_name))
6444            } else {
6445                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6446            };
6447            let Some(target_primary) = target_primary else {
6448                // A dependent or external RHS cannot be canonicalized from the
6449                // indexed graph. Preserve the alias's direct identity instead
6450                // of inventing a target from its source spelling.
6451                return Ok(primary);
6452            };
6453            let Some(target_arguments) = &alias_target.arguments else {
6454                return Ok(target_primary);
6455            };
6456            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6457                .ok_or(CppTemplateResolutionError::Substitution)?;
6458            return self.resolve_template_arguments_inner(
6459                file,
6460                target_primary,
6461                &target_arguments,
6462                seen_aliases,
6463            );
6464        }
6465
6466        let primary_fq_name = self
6467            .cpp_template_metadata
6468            .get(&primary)
6469            .map(|metadata| metadata.primary_fq_name.clone())
6470            .unwrap_or_else(|| primary.fq_name());
6471        let has_specialization_metadata = self
6472            .cpp_template_families
6473            .get(&primary_fq_name)
6474            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6475        if !has_specialization_metadata {
6476            return Ok(primary);
6477        }
6478        self.select_template_specialization(file, &primary, arguments)
6479    }
6480
6481    fn select_template_specialization(
6482        &self,
6483        file: &ProjectFile,
6484        resolved: &CodeUnit,
6485        explicit_arguments: &[CppTemplateExpression],
6486    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6487        let primary_fq_name = self
6488            .cpp_template_metadata
6489            .get(resolved)
6490            .map(|metadata| metadata.primary_fq_name.clone())
6491            .unwrap_or_else(|| resolved.fq_name());
6492        let family = self
6493            .cpp_template_families
6494            .get(&primary_fq_name)
6495            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6496        let primary_candidates = family
6497            .iter()
6498            .filter_map(|unit| {
6499                let metadata = self.cpp_template_metadata.get(unit)?;
6500                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6501            })
6502            .collect::<Vec<_>>();
6503        let primary_unit = primary_candidates
6504            .iter()
6505            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6506            .or_else(|| {
6507                primary_candidates
6508                    .iter()
6509                    .map(|(unit, _)| *unit)
6510                    .min_by_key(|unit| {
6511                        (
6512                            unit.source().to_string(),
6513                            unit.signature().unwrap_or_default(),
6514                        )
6515                    })
6516            })
6517            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6518        let primary_parameters =
6519            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6520                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6521        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6522            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6523
6524        let mut applicable = Vec::new();
6525        for unit in family {
6526            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6527                continue;
6528            };
6529            if metadata.is_primary() || !self.is_visible(file, unit) {
6530                continue;
6531            }
6532            if !cpp_specialization_matches(metadata, &expanded) {
6533                continue;
6534            }
6535            applicable.push((unit, metadata));
6536        }
6537        if applicable.is_empty() {
6538            return Ok(primary_unit.clone());
6539        }
6540
6541        // A scalar constraint count cannot represent C++ partial ordering:
6542        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
6543        // Select only a logical candidate whose structural pattern is strictly
6544        // more specialized than every other distinct applicable candidate.
6545        let winners = applicable
6546            .iter()
6547            .filter(|(candidate, candidate_metadata)| {
6548                applicable.iter().all(|(other, other_metadata)| {
6549                    same_visible_symbol(candidate, other)
6550                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6551                })
6552            })
6553            .copied()
6554            .collect::<Vec<_>>();
6555        let Some((selected, _)) = winners.first() else {
6556            // Mutually incomparable applicable candidates: every one of them
6557            // is a live contender.
6558            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6559                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6560            });
6561        };
6562        if winners
6563            .iter()
6564            .any(|(unit, _)| !same_visible_symbol(unit, selected))
6565        {
6566            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6567                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6568            });
6569        }
6570        Ok((*selected).clone())
6571    }
6572
6573    pub fn resolve_type_components_lexically(
6574        &self,
6575        analyzer: &CppGraphSource<'_>,
6576        file: &ProjectFile,
6577        components: &[String],
6578        global: bool,
6579        lexical_scope: &[String],
6580    ) -> LexicalTypeResolution {
6581        self.resolve_type_components_lexically_inner(
6582            analyzer,
6583            file,
6584            components,
6585            global,
6586            lexical_scope,
6587            TypeCandidateResolution::Canonical,
6588        )
6589    }
6590
6591    pub fn resolve_type_components_lexically_for_forward(
6592        &self,
6593        analyzer: &CppGraphSource<'_>,
6594        file: &ProjectFile,
6595        components: &[String],
6596        global: bool,
6597        lexical_scope: &[String],
6598    ) -> LexicalTypeResolution {
6599        self.resolve_type_components_lexically_inner(
6600            analyzer,
6601            file,
6602            components,
6603            global,
6604            lexical_scope,
6605            TypeCandidateResolution::PreserveAlias,
6606        )
6607    }
6608
6609    pub fn resolve_type_components_lexically_for_target(
6610        &self,
6611        analyzer: &CppGraphSource<'_>,
6612        file: &ProjectFile,
6613        components: &[String],
6614        global: bool,
6615        lexical_scope: &[String],
6616        target: &CodeUnit,
6617    ) -> LexicalTypeResolution {
6618        #[cfg(any(test, feature = "test-support"))]
6619        self.target_preserving_type_resolution_count
6620            .fetch_add(1, Ordering::Relaxed);
6621        self.resolve_type_components_lexically_inner(
6622            analyzer,
6623            file,
6624            components,
6625            global,
6626            lexical_scope,
6627            TypeCandidateResolution::PreserveTarget(target),
6628        )
6629    }
6630
6631    pub fn coarse_unqualified_type_reference_may_resolve(
6632        &self,
6633        file: &ProjectFile,
6634        name: &str,
6635    ) -> bool {
6636        if name.is_empty() {
6637            return true;
6638        }
6639        self.visible_identifier_candidates(file, name)
6640            .any(|candidate| {
6641                candidate.kind() == CodeUnitType::Class
6642                    || declared_type_alias(&self.cpp_source(), candidate)
6643            })
6644            || self.visible_parser_alias_name_is_visible(file, name)
6645    }
6646
6647    #[allow(clippy::too_many_arguments)]
6648    pub fn structured_type_reference_may_resolve_to_target(
6649        &self,
6650        analyzer: &CppGraphSource<'_>,
6651        file: &ProjectFile,
6652        components: &[String],
6653        global: bool,
6654        lexical_scope: &[String],
6655        target: &CodeUnit,
6656    ) -> bool {
6657        if components.is_empty() {
6658            return true;
6659        }
6660        let Some(terminal) = components.last() else {
6661            return true;
6662        };
6663        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6664            .map(|qualified| qualified.join("::"))
6665            .collect::<Vec<_>>();
6666        let target_name = cpp_name_for(target);
6667        if qualified_tiers
6668            .iter()
6669            .any(|qualified| qualified == &target_name)
6670        {
6671            return true;
6672        }
6673
6674        let mut saw_shape_candidate = false;
6675        for candidate in self.visible_identifier_candidates(file, terminal) {
6676            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6677            {
6678                continue;
6679            }
6680            let candidate_name = cpp_name_for(candidate);
6681            let shape_matches = if global || components.len() > 1 {
6682                qualified_tiers
6683                    .iter()
6684                    .any(|qualified| qualified == &candidate_name)
6685            } else {
6686                true
6687            };
6688            if !shape_matches {
6689                continue;
6690            }
6691            saw_shape_candidate = true;
6692            if same_visible_symbol(candidate, target)
6693                || self.c_tag_declaration_family_matches_target(
6694                    analyzer,
6695                    file,
6696                    std::slice::from_ref(&candidate),
6697                    target,
6698                )
6699                || self.compatible_primary_template_redeclarations(candidate, target)
6700                || (declared_type_alias(analyzer, candidate)
6701                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6702            {
6703                return true;
6704            }
6705        }
6706
6707        !saw_shape_candidate
6708    }
6709
6710    /// A C tag's forward declaration and complete definition are one logical
6711    /// type even when their indexed signatures and source files differ. Keep
6712    /// this identity narrow: the complete declaration must be a top-level C
6713    /// tag, every visible candidate must be its physical forward declaration,
6714    /// and the parsed tag kind must agree. Different FQNs, competing complete
6715    /// definitions, aliases, and struct/union mismatches remain ambiguous.
6716    fn cached_c_tag_kind(
6717        &self,
6718        analyzer: &CppGraphSource<'_>,
6719        candidate: &CodeUnit,
6720    ) -> Option<CppCTagKind> {
6721        visibility_memo_value(
6722            &self.c_tag_kind_cache,
6723            &self.reads,
6724            candidate.clone(),
6725            || indexed_c_tag_kind(analyzer, candidate),
6726        )
6727    }
6728
6729    fn cached_unique_c_tag_complete_definition(
6730        &self,
6731        analyzer: &CppGraphSource<'_>,
6732        target: &CodeUnit,
6733        target_tag: CppCTagKind,
6734    ) -> Option<CodeUnit> {
6735        visibility_memo_value(
6736            &self.c_tag_complete_definition_cache,
6737            &self.reads,
6738            target.clone(),
6739            || {
6740                let complete_definitions = analyzer
6741                    .definitions(&target.fq_name())
6742                    .filter(|candidate| {
6743                        candidate.is_class()
6744                            && !declared_type_alias(analyzer, candidate)
6745                            && is_c_source_file(candidate.source())
6746                            && analyzer.parent_of(candidate).is_none()
6747                            && cpp_class_declaration_strength(analyzer, candidate)
6748                                == CppClassDeclarationStrength::Full
6749                            && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6750                    })
6751                    .collect::<HashSet<_>>();
6752                (complete_definitions.len() == 1)
6753                    .then(|| complete_definitions.into_iter().next())
6754                    .flatten()
6755                    .filter(|candidate| same_visible_symbol(candidate, target))
6756            },
6757        )
6758    }
6759
6760    pub fn c_tag_declaration_family_matches_target(
6761        &self,
6762        analyzer: &CppGraphSource<'_>,
6763        visible_from: &ProjectFile,
6764        candidates: &[&CodeUnit],
6765        target: &CodeUnit,
6766    ) -> bool {
6767        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6768            let candidate_evidence = candidates
6769                .iter()
6770                .map(|candidate| {
6771                    (
6772                        candidate.fq_name(),
6773                        candidate.source().rel_path().to_path_buf(),
6774                        cpp_class_declaration_strength(analyzer, candidate),
6775                        indexed_c_tag_kind(analyzer, candidate),
6776                        self.is_physically_visible(visible_from, candidate),
6777                    )
6778                })
6779                .collect::<Vec<_>>();
6780            eprintln!(
6781                "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6782                visible_from.rel_path().display(),
6783                target.fq_name(),
6784                target.source().rel_path().display(),
6785                cpp_class_declaration_strength(analyzer, target),
6786                indexed_c_tag_kind(analyzer, target),
6787            );
6788        }
6789        if candidates.is_empty()
6790            || !target.is_class()
6791            || declared_type_alias(analyzer, target)
6792            || !is_c_source_file(target.source())
6793            || analyzer.parent_of(target).is_some()
6794            || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6795        {
6796            return false;
6797        }
6798        let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6799            return false;
6800        };
6801        if self
6802            .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6803            .is_none()
6804        {
6805            return false;
6806        }
6807        let mut saw_visible_forward = false;
6808        for candidate in candidates.iter().copied() {
6809            if candidate == target {
6810                continue;
6811            }
6812            if !candidate.is_class()
6813                || declared_type_alias(analyzer, candidate)
6814                || candidate.fq_name() != target.fq_name()
6815                || analyzer.parent_of(candidate).is_some()
6816                || cpp_class_declaration_strength(analyzer, candidate)
6817                    != CppClassDeclarationStrength::Forward
6818                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6819                || !self.is_physically_visible(visible_from, candidate)
6820            {
6821                return false;
6822            }
6823            saw_visible_forward = true;
6824        }
6825        saw_visible_forward
6826    }
6827
6828    /// Collapse one visible complete C tag and its visible forward declarations
6829    /// before ordinary lexical resolution sees their different signatures as
6830    /// competing types. A second complete definition, a different FQN/tag
6831    /// kind, or an unknown declaration shape remains ambiguous.
6832    fn unique_c_tag_declaration_family(
6833        &self,
6834        analyzer: &CppGraphSource<'_>,
6835        visible_from: &ProjectFile,
6836        candidates: &[&CodeUnit],
6837    ) -> Option<CodeUnit> {
6838        let first = candidates.first()?;
6839        let target_fq_name = first.fq_name();
6840        let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6841        let mut full = None;
6842        let mut saw_forward = false;
6843        for candidate in candidates.iter().copied() {
6844            if !candidate.is_class()
6845                || declared_type_alias(analyzer, candidate)
6846                || candidate.fq_name() != target_fq_name
6847                || analyzer.parent_of(candidate).is_some()
6848                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6849            {
6850                return None;
6851            }
6852            match cpp_class_declaration_strength(analyzer, candidate) {
6853                CppClassDeclarationStrength::Full
6854                    if is_c_source_file(candidate.source())
6855                        && full.replace(candidate.clone()).is_none() => {}
6856                CppClassDeclarationStrength::Forward
6857                    if self.is_physically_visible(visible_from, candidate) =>
6858                {
6859                    saw_forward = true
6860                }
6861                _ => return None,
6862            }
6863        }
6864        if saw_forward { full } else { None }
6865    }
6866
6867    pub fn target_preserving_reference_namespace(
6868        &self,
6869        analyzer: &CppGraphSource<'_>,
6870        file: &ProjectFile,
6871        identifier: &str,
6872        target: &CodeUnit,
6873    ) -> Option<Vec<String>> {
6874        let mut namespace = None;
6875        for candidate in self.visible_identifier_candidates(file, identifier) {
6876            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6877            {
6878                continue;
6879            }
6880            if !(same_visible_symbol(candidate, target)
6881                || self.compatible_primary_template_redeclarations(candidate, target)
6882                || declared_type_alias(analyzer, candidate)
6883                    && self.structured_alias_primary_preserves_target(
6884                        analyzer, file, candidate, target,
6885                    ))
6886            {
6887                continue;
6888            }
6889            if namespace
6890                .as_ref()
6891                .is_some_and(|existing| existing != candidate.package_name())
6892            {
6893                return None;
6894            }
6895            namespace = Some(candidate.package_name().to_string());
6896        }
6897        let namespace = namespace?;
6898        Some(
6899            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6900                brokk_bifrost_core::analyzer::Language::Cpp,
6901                &namespace,
6902            ),
6903        )
6904    }
6905
6906    pub fn resolve_imported_type_candidate(
6907        &self,
6908        analyzer: &CppGraphSource<'_>,
6909        file: &ProjectFile,
6910        target: &CodeUnit,
6911        target_components: &[String],
6912        direct_target: Option<&CodeUnit>,
6913        preserve_alias: bool,
6914    ) -> LexicalTypeResolution {
6915        let candidates = [target];
6916        let resolution = if preserve_alias {
6917            TypeCandidateResolution::PreserveAlias
6918        } else {
6919            direct_target.map_or(
6920                TypeCandidateResolution::Canonical,
6921                TypeCandidateResolution::PreserveTarget,
6922            )
6923        };
6924        // One candidate goes in, so a failure here is never "choose one of
6925        // these": it is the alias chain leaving the index, which must answer
6926        // missing rather than ambiguous (#1828).
6927        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6928            Ok(unit) => LexicalTypeResolution::Resolved {
6929                unit,
6930                components: target_components.to_vec(),
6931                candidates: vec![target.clone()],
6932            },
6933            Err(failure) => failure.lexical_resolution(),
6934        }
6935    }
6936
6937    fn resolve_type_components_lexically_inner(
6938        &self,
6939        analyzer: &CppGraphSource<'_>,
6940        file: &ProjectFile,
6941        components: &[String],
6942        global: bool,
6943        lexical_scope: &[String],
6944        resolution: TypeCandidateResolution<'_>,
6945    ) -> LexicalTypeResolution {
6946        if components.is_empty() {
6947            return LexicalTypeResolution::Missing;
6948        }
6949        // A C++ class injects its own name into the class scope.  The indexed
6950        // FqName for that declaration is the class path itself (for example,
6951        // `n::raw_hash_set`), not a synthetic child named
6952        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
6953        // requested identifier to every scope component, so they cannot
6954        // represent that injected binding when the enclosing class is the
6955        // closest scope.  Recover the binding from the structured class path
6956        // before allowing lookup to fall through to an outer same-spelled
6957        // declaration.
6958        let mut injected = self.resolve_injected_class_name(
6959            analyzer,
6960            file,
6961            components,
6962            global,
6963            lexical_scope,
6964            resolution,
6965        );
6966        for qualified in lexical_component_tiers(components, global, lexical_scope) {
6967            let prefix_len = qualified.len().saturating_sub(components.len());
6968            if injected
6969                .as_ref()
6970                .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6971            {
6972                return injected
6973                    .take()
6974                    .expect("injected class resolution was just present")
6975                    .1;
6976            }
6977            let qualified_name = qualified.join("::");
6978            let candidates = self
6979                .type_candidates(file, &qualified_name)
6980                .into_iter()
6981                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6982                .collect::<Vec<_>>();
6983            if candidates.is_empty() {
6984                if !global && components.len() == 1 {
6985                    match self.resolve_inherited_type_for_lexical_scope(
6986                        analyzer,
6987                        file,
6988                        &qualified[..prefix_len],
6989                        &components[0],
6990                        resolution,
6991                    ) {
6992                        LexicalTypeResolution::Missing => {}
6993                        inherited => return inherited,
6994                    }
6995                }
6996                continue;
6997            }
6998            let candidates =
6999                self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
7000            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
7001                Ok(unit) => unit,
7002                Err(failure) => return failure.lexical_resolution(),
7003            };
7004            return LexicalTypeResolution::Resolved {
7005                unit,
7006                components: qualified,
7007                candidates: candidates.into_iter().cloned().collect(),
7008            };
7009        }
7010        LexicalTypeResolution::Missing
7011    }
7012
7013    fn resolve_injected_class_name(
7014        &self,
7015        analyzer: &CppGraphSource<'_>,
7016        file: &ProjectFile,
7017        components: &[String],
7018        global: bool,
7019        lexical_scope: &[String],
7020        resolution: TypeCandidateResolution<'_>,
7021    ) -> Option<(usize, LexicalTypeResolution)> {
7022        if global
7023            || components.len() != 1
7024            || file.rel_path().extension().is_some_and(|ext| ext == "c")
7025            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
7026        {
7027            return None;
7028        }
7029        let name = components.first()?;
7030        let mut matches: Vec<&CodeUnit> = Vec::new();
7031        let mut owner_len = 0;
7032        for candidate in self.visible_identifier_candidates(file, name) {
7033            if !candidate.is_class()
7034                || declared_type_alias(analyzer, candidate)
7035                || candidate.identifier() != name
7036            {
7037                continue;
7038            }
7039            let candidate_scope = canonical_cpp_scope_components(candidate);
7040            if candidate_scope.len() > lexical_scope.len()
7041                || !lexical_scope.starts_with(&candidate_scope)
7042                || candidate_scope.last().is_none_or(|last| last != name)
7043            {
7044                continue;
7045            }
7046            if candidate_scope.len() > owner_len {
7047                owner_len = candidate_scope.len();
7048                matches.clear();
7049            }
7050            if candidate_scope.len() == owner_len
7051                && !matches
7052                    .iter()
7053                    .any(|existing| same_logical_symbol(existing, candidate))
7054            {
7055                matches.push(candidate);
7056            }
7057        }
7058        if matches.is_empty() {
7059            return None;
7060        }
7061        // The exact current class boundary is the most important injected-name
7062        // case. Ordinary lexical lookup appends the requested name and can
7063        // otherwise select a synthetic constructor-shaped child such as
7064        // `Portal::Impl::Impl` instead of the enclosing `Portal::Impl` class.
7065        // The global and single-component gates above keep qualified receiver
7066        // and static-qualifier contexts out of this recovery.
7067        let owner_components = lexical_scope[..owner_len].to_vec();
7068        let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
7069        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
7070            Ok(unit) => LexicalTypeResolution::Resolved {
7071                unit,
7072                components: owner_components,
7073                candidates: matches.into_iter().cloned().collect(),
7074            },
7075            Err(failure) => failure.lexical_resolution(),
7076        };
7077        Some((owner_len, resolution))
7078    }
7079
7080    fn resolve_inherited_type_for_lexical_scope(
7081        &self,
7082        analyzer: &CppGraphSource<'_>,
7083        file: &ProjectFile,
7084        lexical_scope: &[String],
7085        name: &str,
7086        resolution: TypeCandidateResolution<'_>,
7087    ) -> LexicalTypeResolution {
7088        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
7089            return LexicalTypeResolution::Missing;
7090        };
7091        let lexical_owner_name = lexical_scope.join("::");
7092        if lexical_owner_name.is_empty() {
7093            return LexicalTypeResolution::Missing;
7094        }
7095        let owner_candidates = self
7096            .type_candidates(file, &lexical_owner_name)
7097            .into_iter()
7098            .filter(|candidate| {
7099                canonical_cpp_name_matches(candidate, &lexical_owner_name)
7100                    && !declared_type_alias(analyzer, candidate)
7101            })
7102            .collect::<Vec<_>>();
7103        if owner_candidates.is_empty() {
7104            return LexicalTypeResolution::Missing;
7105        }
7106        // A visible forward declaration and the physical class definition share
7107        // one FQN, but only the definition owns hierarchy facts. When lookup is
7108        // physically inside that definition, do not let an earlier header
7109        // forward declaration erase its base edges (#2240).
7110        let physical_owner_candidates = owner_candidates
7111            .iter()
7112            .copied()
7113            .filter(|candidate| candidate.source() == file)
7114            .collect::<Vec<_>>();
7115        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
7116            owner_candidates
7117        } else {
7118            physical_owner_candidates
7119        };
7120        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
7121            return LexicalTypeResolution::Ambiguous;
7122        };
7123
7124        let mut frontier = hierarchy
7125            .get_direct_ancestors(&lexical_owner)
7126            .definite()
7127            .to_vec();
7128        let mut visited_owners = HashSet::default();
7129        while !frontier.is_empty() {
7130            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
7131            let mut next_frontier = Vec::new();
7132            for owner in frontier {
7133                if !visited_owners.insert(owner.fq_name()) {
7134                    continue;
7135                }
7136                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
7137                let candidates = self
7138                    .type_candidates(file, &qualified_name)
7139                    .into_iter()
7140                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
7141                    .collect::<Vec<_>>();
7142                if candidates.is_empty() {
7143                    for ancestor in hierarchy.get_direct_ancestors(&owner).definite().to_vec() {
7144                        if !next_frontier
7145                            .iter()
7146                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
7147                        {
7148                            next_frontier.push(ancestor);
7149                        }
7150                    }
7151                    continue;
7152                }
7153                let candidates =
7154                    self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
7155                let unit =
7156                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
7157                        Ok(unit) => unit,
7158                        Err(failure) => return failure.lexical_resolution(),
7159                    };
7160                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
7161            }
7162            if let Some((unit, candidates)) = level_matches.first().cloned() {
7163                let Some(first_declaration) = candidates.first() else {
7164                    return LexicalTypeResolution::Ambiguous;
7165                };
7166                if !level_matches.iter().all(|(_, declarations)| {
7167                    declarations
7168                        .iter()
7169                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
7170                }) {
7171                    return LexicalTypeResolution::Ambiguous;
7172                }
7173                let mut components = lexical_scope.to_vec();
7174                components.push(name.to_string());
7175                return LexicalTypeResolution::Resolved {
7176                    unit,
7177                    components,
7178                    candidates,
7179                };
7180            }
7181            frontier = next_frontier;
7182        }
7183        LexicalTypeResolution::Missing
7184    }
7185
7186    /// Resolve a base class through its injected class name at the nearest
7187    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
7188    ///
7189    /// A base whose canonical full definition cannot be pinned from `file` -
7190    /// a forward declaration the include closure completes with two different
7191    /// full definitions, or an alias chain that leaves the index - stops the
7192    /// walk only when that base is spelled `injected_name`. The mem-initializer
7193    /// names a base by that base's own injected class name, so a base spelled
7194    /// differently can never be the one it names, whichever definition it would
7195    /// have turned out to be; aborting the level on its account instead loses
7196    /// the sibling base that *is* named (#2543). A base that is spelled
7197    /// `injected_name` still fails closed, because choosing a deeper same-named
7198    /// ancestor over it would bind the initializer to the wrong constructor.
7199    /// The skipped base carries its own ancestors out of the walk with it: with
7200    /// no canonical unit, the repeated-base accounting below cannot tell one
7201    /// inherited path through it from two.
7202    pub fn inherited_injected_class_owner(
7203        &self,
7204        analyzer: &CppGraphSource<'_>,
7205        file: &ProjectFile,
7206        enclosing_owner: &CodeUnit,
7207        injected_name: &str,
7208    ) -> Option<CodeUnit> {
7209        let hierarchy = analyzer.type_hierarchy_provider()?;
7210        let mut frontier = hierarchy
7211            .get_direct_ancestors(enclosing_owner)
7212            .definite()
7213            .to_vec();
7214        let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
7215        while !frontier.is_empty() {
7216            let mut level_matches = Vec::new();
7217            let mut next_frontier = Vec::new();
7218            for raw_owner in frontier {
7219                let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
7220                else {
7221                    if raw_owner.identifier() == injected_name {
7222                        return None;
7223                    }
7224                    continue;
7225                };
7226                let propagated = propagated_counts.entry(owner.clone()).or_default();
7227                if *propagated == 2 {
7228                    continue;
7229                }
7230                *propagated += 1;
7231                if owner.identifier() == injected_name {
7232                    level_matches.push(owner.clone());
7233                }
7234                next_frontier.extend(hierarchy.get_direct_ancestors(&owner).definite().to_vec());
7235            }
7236            match level_matches.as_slice() {
7237                [owner] => return Some(owner.clone()),
7238                [_, ..] => return None,
7239                [] => {}
7240            }
7241            frontier = next_frontier;
7242        }
7243        None
7244    }
7245
7246    /// The one type the candidates name under `resolution`, or why they do not
7247    /// name one. The two preserving modes only ever reject candidates that
7248    /// disagree with each other, which is ambiguity; canonicalization can also
7249    /// fail because the alias chain leaves the index (#1828).
7250    fn resolve_type_candidates(
7251        &self,
7252        analyzer: &CppGraphSource<'_>,
7253        file: &ProjectFile,
7254        candidates: &[&CodeUnit],
7255        resolution: TypeCandidateResolution<'_>,
7256    ) -> Result<CodeUnit, TypeCandidateFailure> {
7257        if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
7258            && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
7259        {
7260            return Ok(unit);
7261        }
7262        match resolution {
7263            TypeCandidateResolution::Canonical => {
7264                self.canonical_type_candidate_resolution(analyzer, file, candidates)
7265            }
7266            TypeCandidateResolution::PreserveAlias => {
7267                // A generated index can retain identical alias spellings from
7268                // mutually exclusive headers. When the reference file
7269                // physically reaches exactly one of those source declarations,
7270                // include closure is the structured evidence that selects it;
7271                // treating the two source spellings as an overload set makes a
7272                // reachable alias appear ambiguous (#1844).
7273                let same_fqn_alias_family = candidates.len() > 1
7274                    && candidates.iter().all(|candidate| {
7275                        declared_type_alias(analyzer, candidate)
7276                            && same_logical_symbol(candidates[0], candidate)
7277                    })
7278                    && candidates
7279                        .iter()
7280                        .any(|candidate| candidate.source() != candidates[0].source());
7281                if same_fqn_alias_family {
7282                    let physically_visible = candidates
7283                        .iter()
7284                        .copied()
7285                        .filter(|candidate| self.is_physically_visible(file, candidate))
7286                        .collect::<Vec<_>>();
7287                    // The family is one logical declaration only when the
7288                    // reachable spellings agree. Two same-FQN aliases whose
7289                    // written targets differ (`using Choice = Canonical;` in
7290                    // one header, `using Choice = ::Canonical;` in another)
7291                    // are a genuine conflict, and choosing the first indexed
7292                    // one silently binds the reference to an arbitrary owner
7293                    // (#2398). Collapse only a single reachable declaration
7294                    // or reachable declarations with one structured target;
7295                    // everything else stays ambiguous below.
7296                    if let Some(alias) = select_alias_spelling_with_agreed_targets(
7297                        &physically_visible,
7298                        |candidate| self.structured_alias_target(analyzer, candidate),
7299                    ) {
7300                        return Ok(alias.clone());
7301                    }
7302                }
7303                unique_type_candidate_preserving_alias(analyzer, file, candidates)
7304                    .ok_or(TypeCandidateFailure::Ambiguous)
7305            }
7306            TypeCandidateResolution::PreserveTarget(target) => self
7307                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
7308                .ok_or(TypeCandidateFailure::Ambiguous),
7309        }
7310    }
7311
7312    fn candidates_for_type_resolution<'b>(
7313        &self,
7314        analyzer: &CppGraphSource<'_>,
7315        file: &ProjectFile,
7316        candidates: &[&'b CodeUnit],
7317        resolution: TypeCandidateResolution<'_>,
7318    ) -> Vec<&'b CodeUnit> {
7319        if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
7320            let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
7321            if compile_proven.len() == 1 {
7322                return compile_proven;
7323            }
7324        }
7325        candidates.to_vec()
7326    }
7327
7328    /// Narrow a same-name forward lookup to the declaration selected by the
7329    /// translation unit's compile command. The lexical resolver intentionally
7330    /// does not receive a reference node, so this is the only compile-context
7331    /// evidence available at that stage. A single selected candidate is safe:
7332    /// the caller still checks include activation and the reference's own
7333    /// guards before reporting the result as visible.
7334    fn compile_proven_type_candidates<'b>(
7335        &self,
7336        analyzer: &CppGraphSource<'_>,
7337        file: &ProjectFile,
7338        candidates: &[&'b CodeUnit],
7339    ) -> Vec<&'b CodeUnit> {
7340        let proven = self.compile_proven_guards(file);
7341        if proven.is_empty() {
7342            return Vec::new();
7343        }
7344        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
7345            return Vec::new();
7346        };
7347        candidates
7348            .iter()
7349            .copied()
7350            .filter(|candidate| {
7351                let declaration_guards =
7352                    declaration_guard_requirements(analyzer, self.cpp, candidate);
7353                if declaration_guards.is_empty() {
7354                    return false;
7355                }
7356                if candidate.source() == file {
7357                    return declaration_guards.iter().any(|(_, required)| {
7358                        guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
7359                    });
7360                }
7361                declaration_guards.iter().any(|(_, required)| {
7362                    self.foreign_declaration_reachable_from_compile_proven_guards(
7363                        file,
7364                        prepared.as_ref(),
7365                        candidate.source(),
7366                        required,
7367                        usize::MAX,
7368                    )
7369                })
7370            })
7371            .collect()
7372    }
7373
7374    pub fn resolve_callable_value_components_lexically(
7375        &self,
7376        analyzer: &CppGraphSource<'_>,
7377        file: &ProjectFile,
7378        owner_components: &[String],
7379        member_name: &str,
7380        global: bool,
7381        lexical_scope: &[String],
7382    ) -> LexicalCallableValueResolution {
7383        if owner_components.is_empty() || member_name.is_empty() {
7384            return LexicalCallableValueResolution::Missing;
7385        }
7386        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7387            let owner_name = qualified_owner.join("::");
7388            let type_candidates = self
7389                .type_candidates(file, &owner_name)
7390                .into_iter()
7391                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7392                .collect::<Vec<_>>();
7393            let resolved_type = if type_candidates.is_empty() {
7394                None
7395            } else {
7396                let Some(unit) =
7397                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7398                else {
7399                    return LexicalCallableValueResolution::Ambiguous;
7400                };
7401                Some(unit)
7402            };
7403
7404            let mut qualified_callable = qualified_owner;
7405            qualified_callable.push(member_name.to_string());
7406            let callable_name = qualified_callable.join("::");
7407            let free_function = self
7408                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7409                .into_iter()
7410                .find(|candidate| {
7411                    canonical_cpp_name_matches(candidate, &callable_name)
7412                        && type_owner_of(analyzer, candidate).is_none()
7413                })
7414                .cloned();
7415
7416            match (resolved_type, free_function) {
7417                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7418                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7419                (None, Some(function)) => {
7420                    return LexicalCallableValueResolution::FreeFunction(function);
7421                }
7422                (None, None) => {}
7423            }
7424        }
7425        LexicalCallableValueResolution::Missing
7426    }
7427
7428    fn resolve_type_for_declaration(
7429        &self,
7430        visible_from: &ProjectFile,
7431        declaration: &CodeUnit,
7432        raw_name: &str,
7433    ) -> Option<CodeUnit> {
7434        let normalized = normalize_reference_name(raw_name)?;
7435        if !normalized.contains("::")
7436            && let Some(namespace) = cpp_namespace_for(declaration)
7437        {
7438            for prefix in namespace_prefixes(&namespace) {
7439                let qualified = format!("{prefix}::{normalized}");
7440                if let Some(unit) = self
7441                    .type_candidates(visible_from, &qualified)
7442                    .into_iter()
7443                    .next()
7444                {
7445                    return Some(unit.clone());
7446                }
7447            }
7448        }
7449        self.resolve_type(visible_from, raw_name)
7450    }
7451
7452    fn resolve_unique_canonical_type_for_declaration(
7453        &self,
7454        analyzer: &CppGraphSource<'_>,
7455        visible_from: &ProjectFile,
7456        declaration: &CodeUnit,
7457        raw_name: &str,
7458    ) -> Option<CodeUnit> {
7459        let current = self.resolve_defining_type_for_declaration(
7460            analyzer,
7461            visible_from,
7462            declaration,
7463            raw_name,
7464        )?;
7465        self.canonical_type_resolution(analyzer, visible_from, &current)
7466            .ok()
7467    }
7468
7469    pub fn canonical_type_unit(
7470        &self,
7471        analyzer: &CppGraphSource<'_>,
7472        visible_from: &ProjectFile,
7473        unit: &CodeUnit,
7474    ) -> Option<CodeUnit> {
7475        self.canonical_type_resolution(analyzer, visible_from, unit)
7476            .ok()
7477    }
7478
7479    /// Follow an alias only when it is visible at `reference`.
7480    ///
7481    /// The consumer need not spell the alias target. In particular, a
7482    /// conditional include can make `PublicPtr` visible at the reference while
7483    /// ordinary physical-include visibility is false. Prove the public alias
7484    /// with the reference's guard environment, then use the existing structured
7485    /// alias-chain resolver over the consumer's bounded declaration index.
7486    pub fn canonical_type_unit_in_context(
7487        &self,
7488        analyzer: &CppGraphSource<'_>,
7489        visible_from: &ProjectFile,
7490        reference: Node<'_>,
7491        unit: &CodeUnit,
7492    ) -> Option<CodeUnit> {
7493        if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7494        {
7495            return None;
7496        }
7497        self.canonical_type_resolution(analyzer, visible_from, unit)
7498            .ok()
7499    }
7500
7501    /// Canonicalize an alias chain at `reference` after its guards select
7502    /// conditional alias arms (#6902).
7503    ///
7504    /// `#if X using T = A; #else using T = B; #endif` indexes one alias
7505    /// declaration per arm. Canonicalizing the arms together reports `A` and
7506    /// `B` as an ambiguity everywhere, and following the first arm of a nested
7507    /// family attributes every use to `A`. Each arm, at every hop of the chain,
7508    /// therefore faces the reference's own guards plus the defines the build
7509    /// proves before its target is followed. An arm whose guards the evidence
7510    /// implies is proven when it is also visible at the reference; an arm
7511    /// whose guards are only compatible stays a candidate; a contradicted arm
7512    /// is dropped. Compatibility never establishes proof.
7513    ///
7514    /// Returns `None` when no hop of the chain is a family of alias arms with
7515    /// different targets; ordinary canonicalization owns that case.
7516    pub fn guarded_alias_selection_at_reference(
7517        &self,
7518        analyzer: &CppGraphSource<'_>,
7519        file: &ProjectFile,
7520        reference: Node<'_>,
7521        candidates: &[CodeUnit],
7522    ) -> Option<GuardedAliasSelection> {
7523        const MAX_GUARDED_ALIAS_STEPS: usize = 64;
7524        let mut conditional = false;
7525        let mut complete = true;
7526        let mut selected = Vec::new();
7527        let mut terminals: Vec<(CodeUnit, bool)> = Vec::new();
7528        let mut visited = HashSet::default();
7529        // Each pending entry is a unit, whether every hop before it was
7530        // proven, its hop depth, and whether it is a top-level candidate.
7531        let mut pending = candidates
7532            .iter()
7533            .map(|candidate| (candidate.clone(), true, 0, true))
7534            .collect::<Vec<_>>();
7535        let mut steps = 0;
7536        while let Some((unit, proven, depth, top_level)) = pending.pop() {
7537            if !visited.insert((unit.clone(), proven)) {
7538                continue;
7539            }
7540            steps += 1;
7541            if steps > MAX_GUARDED_ALIAS_STEPS || depth > MAX_COMPARABLE_ALIAS_HOPS {
7542                return None;
7543            }
7544            let arms = match self.conditional_alias_arms(analyzer, file, &unit) {
7545                Some(arms) => {
7546                    conditional = true;
7547                    let evidence = arms
7548                        .into_iter()
7549                        .map(|arm| {
7550                            let evidence = self.alias_arm_evidence(analyzer, file, &arm, reference);
7551                            (arm, evidence)
7552                        })
7553                        .collect::<Vec<_>>();
7554                    // A translation unit declares at most one arm of an alias
7555                    // with conflicting targets, so an implied arm excludes
7556                    // every merely compatible one.
7557                    let implied = evidence
7558                        .iter()
7559                        .any(|(_, evidence)| *evidence == AliasArmEvidence::Implied);
7560                    for (arm, _) in &evidence {
7561                        visited.insert((arm.clone(), proven));
7562                    }
7563                    evidence
7564                        .into_iter()
7565                        .filter_map(|(arm, evidence)| match evidence {
7566                            AliasArmEvidence::Implied => Some((arm, proven)),
7567                            AliasArmEvidence::Compatible if !implied => Some((arm, false)),
7568                            AliasArmEvidence::Compatible | AliasArmEvidence::Contradicted => None,
7569                        })
7570                        .collect::<Vec<_>>()
7571                }
7572                None => vec![(unit, proven)],
7573            };
7574            for (arm, proven) in arms {
7575                if top_level {
7576                    selected.push(arm.clone());
7577                }
7578                match self.structured_alias_target(analyzer, &arm) {
7579                    None | Some(StructuredAliasTarget::Builtin) if arm.is_class() => {
7580                        terminals.push((arm, proven));
7581                    }
7582                    None | Some(StructuredAliasTarget::Builtin) => complete = false,
7583                    Some(target) => {
7584                        match self.structured_alias_target_resolution(file, &arm, &target) {
7585                            Ok(next) => pending.push((next, proven, depth + 1, false)),
7586                            Err(_) => complete = false,
7587                        }
7588                    }
7589                }
7590            }
7591        }
7592        if !conditional {
7593            return None;
7594        }
7595        let mut targets: Vec<CodeUnit> = Vec::new();
7596        for (terminal, _) in &terminals {
7597            if !targets
7598                .iter()
7599                .any(|existing| same_visible_symbol(existing, terminal))
7600            {
7601                targets.push(terminal.clone());
7602            }
7603        }
7604        if complete
7605            && let [unit] = targets.as_slice()
7606            && terminals.iter().all(|(_, proven)| *proven)
7607        {
7608            return Some(GuardedAliasSelection::Proven {
7609                unit: unit.clone(),
7610                candidates: selected,
7611            });
7612        }
7613        Some(GuardedAliasSelection::Alternatives(targets))
7614    }
7615
7616    /// Every visible arm of `unit`'s alias family when the arms disagree about
7617    /// their target. Template aliases keep their own argument machinery.
7618    fn conditional_alias_arms(
7619        &self,
7620        analyzer: &CppGraphSource<'_>,
7621        file: &ProjectFile,
7622        unit: &CodeUnit,
7623    ) -> Option<Vec<CodeUnit>> {
7624        if !declared_type_alias(analyzer, unit) || self.cpp_template_metadata.contains_key(unit) {
7625            return None;
7626        }
7627        let mut arms = Vec::new();
7628        for candidate in self
7629            .visible_identifier_candidates(file, unit.identifier())
7630            .filter(|candidate| {
7631                candidate.kind() == unit.kind()
7632                    && candidate.fq_name() == unit.fq_name()
7633                    && declared_type_alias(analyzer, candidate)
7634            })
7635        {
7636            if self.cpp_template_metadata.contains_key(candidate) {
7637                return None;
7638            }
7639            if !arms.contains(candidate) {
7640                arms.push(candidate.clone());
7641            }
7642        }
7643        let refs = arms.iter().collect::<Vec<_>>();
7644        (arms.len() > 1
7645            && select_alias_spelling_with_agreed_targets(&refs, |candidate| {
7646                self.structured_alias_target(analyzer, candidate)
7647            })
7648            .is_none())
7649        .then_some(arms)
7650    }
7651
7652    /// Item 8's proof boundary for one alias arm: implication by the
7653    /// reference's effective guards proves the arm,
7654    /// compatibility only keeps it a candidate (#6902).
7655    fn alias_arm_evidence(
7656        &self,
7657        analyzer: &CppGraphSource<'_>,
7658        file: &ProjectFile,
7659        arm: &CodeUnit,
7660        reference: Node<'_>,
7661    ) -> AliasArmEvidence {
7662        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
7663            return AliasArmEvidence::Compatible;
7664        };
7665        let effective = self.effective_reference_guards(
7666            file,
7667            preprocessor_guard_environment(reference, prepared.source()).as_ref(),
7668        );
7669        let requirements = declaration_guard_requirements(analyzer, self.cpp, arm);
7670        if requirements
7671            .iter()
7672            .any(|(_, required)| guard_requirements_hold_at_reference(required, effective.as_ref()))
7673            && self.external_type_candidate_visible_in_context(analyzer, file, arm, reference)
7674        {
7675            AliasArmEvidence::Implied
7676        } else if requirements.is_empty()
7677            || requirements
7678                .iter()
7679                .any(|(_, required)| guards_compatible_at_reference(required, effective.as_ref()))
7680        {
7681            AliasArmEvidence::Compatible
7682        } else {
7683            AliasArmEvidence::Contradicted
7684        }
7685    }
7686
7687    /// Follow `unit`'s alias chain to the class it names, or report why the
7688    /// chain does not end at one indexed class.
7689    ///
7690    /// A chain that leaves the index - an alias to a template parameter, to a
7691    /// standard-library type, or to any other declaration the workspace does
7692    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
7693    /// there is still nothing to choose between.
7694    fn canonical_type_resolution(
7695        &self,
7696        analyzer: &CppGraphSource<'_>,
7697        visible_from: &ProjectFile,
7698        unit: &CodeUnit,
7699    ) -> Result<CodeUnit, TypeCandidateFailure> {
7700        let mut current = unit.clone();
7701        let mut seen_aliases = HashSet::default();
7702        loop {
7703            let Some(target) = self.structured_alias_target(analyzer, &current) else {
7704                return current
7705                    .is_class()
7706                    .then_some(current)
7707                    .ok_or(TypeCandidateFailure::Unresolvable);
7708            };
7709            if matches!(target, StructuredAliasTarget::Builtin) {
7710                return current
7711                    .is_class()
7712                    .then_some(current)
7713                    .ok_or(TypeCandidateFailure::Unresolvable);
7714            }
7715            if !seen_aliases.insert(current.clone()) {
7716                return Err(TypeCandidateFailure::Unresolvable);
7717            }
7718            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
7719        }
7720    }
7721
7722    pub fn canonical_visible_full_type_unit(
7723        &self,
7724        analyzer: &CppGraphSource<'_>,
7725        visible_from: &ProjectFile,
7726        unit: &CodeUnit,
7727    ) -> Option<CodeUnit> {
7728        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7729        if cpp_class_declaration_strength(analyzer, &canonical)
7730            != CppClassDeclarationStrength::Forward
7731        {
7732            return Some(canonical);
7733        }
7734        let mut full = Vec::new();
7735        for candidate in self
7736            .visible_identifier_candidates(visible_from, canonical.identifier())
7737            .filter(|candidate| {
7738                candidate.is_class()
7739                    && candidate.fq_name() == canonical.fq_name()
7740                    && cpp_class_declaration_strength(analyzer, candidate)
7741                        == CppClassDeclarationStrength::Full
7742            })
7743        {
7744            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7745                full.push(candidate.clone());
7746            }
7747        }
7748        match full.len() {
7749            0 => Some(canonical),
7750            1 => full.pop(),
7751            _ => None,
7752        }
7753    }
7754
7755    fn resolve_structured_alias_target(
7756        &self,
7757        visible_from: &ProjectFile,
7758        declaration: &CodeUnit,
7759        target: &StructuredAliasTarget,
7760    ) -> Option<CodeUnit> {
7761        self.structured_alias_target_resolution(visible_from, declaration, target)
7762            .ok()
7763    }
7764
7765    fn structured_alias_target_resolution(
7766        &self,
7767        visible_from: &ProjectFile,
7768        declaration: &CodeUnit,
7769        target: &StructuredAliasTarget,
7770    ) -> Result<CodeUnit, TypeCandidateFailure> {
7771        let primary =
7772            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7773        let StructuredAliasTarget::Named { arguments, .. } = target else {
7774            return Err(TypeCandidateFailure::Unresolvable);
7775        };
7776        match arguments {
7777            Some(arguments) => self
7778                .resolve_template_arguments(visible_from, primary, arguments)
7779                .map_err(|error| match error {
7780                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7781                        TypeCandidateFailure::Ambiguous
7782                    }
7783                    _ => TypeCandidateFailure::Unresolvable,
7784                }),
7785            None => Ok(primary),
7786        }
7787    }
7788
7789    fn resolve_structured_alias_primary(
7790        &self,
7791        visible_from: &ProjectFile,
7792        declaration: &CodeUnit,
7793        target: &StructuredAliasTarget,
7794    ) -> Option<CodeUnit> {
7795        self.structured_alias_primary_resolution(visible_from, declaration, target)
7796            .ok()
7797    }
7798
7799    fn structured_alias_primary_resolution(
7800        &self,
7801        visible_from: &ProjectFile,
7802        declaration: &CodeUnit,
7803        target: &StructuredAliasTarget,
7804    ) -> Result<CodeUnit, TypeCandidateFailure> {
7805        let StructuredAliasTarget::Named {
7806            components, global, ..
7807        } = target
7808        else {
7809            return Err(TypeCandidateFailure::Unresolvable);
7810        };
7811        let qualified = components.join("::");
7812        let candidates = if *global {
7813            // `::A::B` anchors at the root scope, so a candidate whose
7814            // canonical path merely ends with the spelled components does not
7815            // qualify. Without this filter a global `::Canonical` target also
7816            // collects `alpha::Canonical`, the lookup reports a false
7817            // ambiguity, and the alias arm silently drops out of its
7818            // conflicting family instead of proving the conflict (#2398).
7819            let mut candidates = self.type_candidates(visible_from, &qualified);
7820            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7821            candidates
7822        } else {
7823            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7824        };
7825        logical_type_candidate(candidates)
7826    }
7827
7828    pub fn structured_alias_primary_preserves_target(
7829        &self,
7830        analyzer: &CppGraphSource<'_>,
7831        visible_from: &ProjectFile,
7832        candidate: &CodeUnit,
7833        target: &CodeUnit,
7834    ) -> bool {
7835        let mut current = candidate.clone();
7836        let mut seen = HashSet::default();
7837        let mut matched_target = false;
7838        loop {
7839            if same_visible_symbol(&current, target)
7840                || self.compatible_primary_template_redeclarations(&current, target)
7841            {
7842                matched_target = true;
7843            }
7844            if !seen.insert(current.clone()) {
7845                return false;
7846            }
7847            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7848                return matched_target;
7849            };
7850            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7851                return matched_target;
7852            };
7853            let Some(primary) =
7854                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7855            else {
7856                // A dependent member target such as `Detector<T>::type`
7857                // cannot be reduced to an indexed primary, but a preceding
7858                // structured alias hop may already have proven the requested
7859                // alias identity. Cycles still resolve a primary and are
7860                // rejected by `seen` above.
7861                return matched_target;
7862            };
7863            current = primary;
7864        }
7865    }
7866
7867    fn lexical_type_candidates_match_target(
7868        &self,
7869        analyzer: &CppGraphSource<'_>,
7870        unit: &CodeUnit,
7871        candidates: &[CodeUnit],
7872        target: &CodeUnit,
7873    ) -> bool {
7874        same_visible_symbol(unit, target)
7875            || self.same_template_member_identity(analyzer, unit, target)
7876            || candidates.iter().any(|candidate| {
7877                same_visible_symbol(candidate, target)
7878                    || self.same_template_member_identity(analyzer, candidate, target)
7879            })
7880    }
7881
7882    pub fn structured_class_alias_resolves_to_target(
7883        &self,
7884        analyzer: &CppGraphSource<'_>,
7885        visible_from: &ProjectFile,
7886        alias: &CodeUnit,
7887        target: &CodeUnit,
7888    ) -> bool {
7889        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7890            return false;
7891        };
7892        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7893            return false;
7894        };
7895        let StructuredAliasTarget::Named {
7896            components, global, ..
7897        } = &alias_target
7898        else {
7899            return false;
7900        };
7901        let lexical_scope = canonical_cpp_scope_components(&owner);
7902        match self.resolve_type_components_lexically_for_target(
7903            analyzer,
7904            visible_from,
7905            components,
7906            *global,
7907            &lexical_scope,
7908            target,
7909        ) {
7910            LexicalTypeResolution::Resolved {
7911                unit, candidates, ..
7912            } => self.lexical_type_candidates_match_target(analyzer, &unit, &candidates, target),
7913            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7914                self.structured_alias_primary_preserves_target(
7915                    analyzer,
7916                    visible_from,
7917                    alias,
7918                    target,
7919                ) || self.flattened_macro_namespace_alias_target_matches(
7920                    analyzer,
7921                    visible_from,
7922                    alias,
7923                    &alias_target,
7924                    target,
7925                )
7926            }
7927        }
7928    }
7929
7930    /// Return true when a class-owned alias names the requested type as one
7931    /// structured qualifier in its target path.
7932    ///
7933    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
7934    /// indexed class. Forward lookup can still retain `Primary` as its bounded
7935    /// canonical identity. Inverse lookup needs the same evidence when later
7936    /// references use only the alias spelling.
7937    pub fn structured_class_alias_path_preserves_target(
7938        &self,
7939        analyzer: &CppGraphSource<'_>,
7940        visible_from: &ProjectFile,
7941        alias: &CodeUnit,
7942        target: &CodeUnit,
7943    ) -> bool {
7944        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7945            return false;
7946        };
7947        let Some(StructuredAliasTarget::Named {
7948            components, global, ..
7949        }) = self.structured_alias_target(analyzer, alias)
7950        else {
7951            return false;
7952        };
7953        let lexical_scope = canonical_cpp_scope_components(&owner);
7954        (1..components.len()).rev().any(|component_count| {
7955            matches!(
7956                self.resolve_type_components_lexically_for_target(
7957                    analyzer,
7958                    visible_from,
7959                    &components[..component_count],
7960                    global,
7961                    &lexical_scope,
7962                    target,
7963                ),
7964                LexicalTypeResolution::Resolved {
7965                    ref unit,
7966                    ref candidates,
7967                    ..
7968                } if self.lexical_type_candidates_match_target(
7969                    analyzer,
7970                    unit,
7971                    candidates,
7972                    target,
7973                )
7974            )
7975        })
7976    }
7977
7978    fn flattened_macro_namespace_alias_target_matches(
7979        &self,
7980        analyzer: &CppGraphSource<'_>,
7981        visible_from: &ProjectFile,
7982        alias: &CodeUnit,
7983        alias_target: &StructuredAliasTarget,
7984        target: &CodeUnit,
7985    ) -> bool {
7986        let StructuredAliasTarget::Named {
7987            components,
7988            global: false,
7989            arguments: None,
7990        } = alias_target
7991        else {
7992            return false;
7993        };
7994        let Some((target_name, namespace_components)) = components.split_last() else {
7995            return false;
7996        };
7997        if namespace_components.is_empty()
7998            || target_name != target.identifier()
7999            || alias.source() != target.source()
8000            || alias.source() != visible_from
8001            || !target.is_class()
8002            || declared_type_alias(analyzer, target)
8003        {
8004            return false;
8005        }
8006        if self
8007            .resolve_structured_alias_target(visible_from, alias, alias_target)
8008            .is_some()
8009        {
8010            return false;
8011        }
8012
8013        let Some(alias_start) = analyzer
8014            .declaration_source_occurrences(alias)
8015            .into_iter()
8016            .flatten()
8017            .map(|occurrence| occurrence.range.start_byte)
8018            .min()
8019        else {
8020            return false;
8021        };
8022        let has_matching_declaration = analyzer
8023            .declaration_source_occurrences(target)
8024            .into_iter()
8025            .flatten()
8026            .any(|occurrence| {
8027                occurrence.range.end_byte <= alias_start
8028                    && occurrence.fact.flattened_macro_namespace.as_deref()
8029                        == Some(namespace_components)
8030            });
8031        if !has_matching_declaration {
8032            return false;
8033        }
8034
8035        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
8036        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
8037        guard_requirement_sets_match(&alias_guards, &target_guards)
8038    }
8039
8040    pub fn template_alias_arguments_preserve_target(
8041        &self,
8042        analyzer: &CppGraphSource<'_>,
8043        visible_from: &ProjectFile,
8044        alias: &CodeUnit,
8045        arguments: &[CppTemplateExpression],
8046        target: &CodeUnit,
8047    ) -> bool {
8048        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
8049            return false;
8050        };
8051        if metadata.alias_target.is_none()
8052            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
8053        {
8054            return false;
8055        }
8056        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
8057    }
8058
8059    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
8060        self.cpp_template_metadata
8061            .get(unit)
8062            .is_some_and(CppTemplateMetadata::is_primary)
8063    }
8064
8065    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
8066        self.cpp_template_metadata
8067            .get(unit)
8068            .is_some_and(CppTemplateMetadata::is_specialization)
8069    }
8070
8071    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
8072        same_visible_symbol(left, right)
8073            || self.compatible_primary_template_redeclarations(left, right)
8074    }
8075
8076    pub fn same_template_member_identity(
8077        &self,
8078        analyzer: &CppGraphSource<'_>,
8079        left: &CodeUnit,
8080        right: &CodeUnit,
8081    ) -> bool {
8082        if same_visible_symbol(left, right) {
8083            return true;
8084        }
8085        if left.kind() != right.kind()
8086            || left.identifier() != right.identifier()
8087            || left.signature() != right.signature()
8088        {
8089            return false;
8090        }
8091        let (Some(left_owner), Some(right_owner)) =
8092            (analyzer.parent_of(left), analyzer.parent_of(right))
8093        else {
8094            return false;
8095        };
8096        left_owner.is_class()
8097            && right_owner.is_class()
8098            && self.same_template_owner_identity(&left_owner, &right_owner)
8099    }
8100
8101    fn unique_canonical_type_candidate(
8102        &self,
8103        analyzer: &CppGraphSource<'_>,
8104        visible_from: &ProjectFile,
8105        candidates: &[&CodeUnit],
8106    ) -> Option<CodeUnit> {
8107        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
8108            .ok()
8109    }
8110
8111    fn canonical_type_candidate_resolution(
8112        &self,
8113        analyzer: &CppGraphSource<'_>,
8114        visible_from: &ProjectFile,
8115        candidates: &[&CodeUnit],
8116    ) -> Result<CodeUnit, TypeCandidateFailure> {
8117        let mut canonical = Vec::new();
8118        for candidate in candidates {
8119            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
8120            if canonical
8121                .iter()
8122                .any(|existing| same_visible_symbol(existing, &resolved))
8123            {
8124                continue;
8125            }
8126            if let Some(existing) = canonical.iter_mut().find(|existing| {
8127                self.compatible_primary_template_redeclarations(existing, &resolved)
8128            }) {
8129                // A forward declaration and its full primary-template
8130                // definition are one C++ type even when they live in
8131                // different headers and alpha-rename their parameters. The
8132                // target-preserving path already reconciles this family; do
8133                // the same for ordinary canonical lookup so an out-of-line
8134                // member's lexical owner is not made ambiguous by its own
8135                // forward declaration. Retain the strongest physical
8136                // declaration for later owner/range queries.
8137                if matches!(
8138                    (
8139                        cpp_class_declaration_strength(analyzer, existing),
8140                        cpp_class_declaration_strength(analyzer, &resolved),
8141                    ),
8142                    (
8143                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
8144                        CppClassDeclarationStrength::Full,
8145                    ) | (
8146                        CppClassDeclarationStrength::Unknown,
8147                        CppClassDeclarationStrength::Forward,
8148                    )
8149                ) {
8150                    *existing = resolved;
8151                }
8152                continue;
8153            }
8154            canonical.push(resolved);
8155            if canonical.len() > 1 {
8156                return Err(TypeCandidateFailure::Ambiguous);
8157            }
8158        }
8159        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
8160    }
8161
8162    pub fn unique_type_candidate_preserving_target(
8163        &self,
8164        analyzer: &CppGraphSource<'_>,
8165        visible_from: &ProjectFile,
8166        candidates: &[&CodeUnit],
8167        target: &CodeUnit,
8168    ) -> Option<CodeUnit> {
8169        // C++ headers often expose one logical type through mutually exclusive
8170        // physical declarations, for example a class in the fallback branch
8171        // and a `using` alias to the standard-library type in the configured
8172        // branch. The index intentionally retains both declarations so forward
8173        // lookup can report each target. Preserve the requested target when
8174        // that is the only ambiguity: every candidate has the same type kind,
8175        // exact canonical FQN, and source file, and the requested declaration
8176        // itself is one of the physical candidates. Do not merge same-named
8177        // declarations from different files or namespaces; those remain
8178        // ambiguous and fail closed below.
8179        if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
8180            || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
8181        {
8182            return Some(target.clone());
8183        }
8184        let mut resolved_candidates = Vec::new();
8185        for candidate in candidates {
8186            // An ifdef branch that aliases an unindexed system type (for
8187            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
8188            // canonicalized. That branch does not name `target`. Dropping it
8189            // keeps the branch that does. Failing the whole family here would
8190            // deny every usage of the reachable spelling (#2368).
8191            let Some(resolved) =
8192                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
8193            else {
8194                continue;
8195            };
8196            if resolved_candidates
8197                .iter()
8198                .any(|existing| same_visible_symbol(existing, &resolved))
8199            {
8200                continue;
8201            }
8202            resolved_candidates.push(resolved);
8203        }
8204        match resolved_candidates.as_slice() {
8205            [] => None,
8206            [single] => Some(single.clone()),
8207            // The branches disagree about what the name aliases. When they are
8208            // spellings of one entity (#1845) that disagreement is a build
8209            // configuration, not a choice between types, so it must not deny
8210            // the requested target its reference.
8211            _ => self
8212                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
8213                .map(|_| target.clone()),
8214        }
8215    }
8216
8217    /// The declaration a same-file same-FQN family stands for when a reference
8218    /// names `target`, or `None` when the candidates are not one family or the
8219    /// family does not name `target`.
8220    ///
8221    /// A translation unit cannot hold two different types under one qualified
8222    /// name, so several same-kind declarations of one FQN in one file are
8223    /// alternate spellings of one entity - the configuration branches of an
8224    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
8225    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
8226    /// targets differ; canonicalizing each branch on its own and then demanding
8227    /// agreement reports an ambiguity that denies every declaration in the
8228    /// family its usages (#1845). The family names `target` when it declares
8229    /// it, or when one branch's alias chain reaches it.
8230    ///
8231    /// Declarations in different files or namespaces are distinct entities and
8232    /// are deliberately excluded: their disagreement is a real ambiguity.
8233    pub fn same_fqn_type_spelling_for_target<'b>(
8234        &self,
8235        analyzer: &CppGraphSource<'_>,
8236        visible_from: &ProjectFile,
8237        candidates: &[&'b CodeUnit],
8238        target: &CodeUnit,
8239    ) -> Option<&'b CodeUnit> {
8240        let [first, rest @ ..] = candidates else {
8241            return None;
8242        };
8243        if rest.is_empty()
8244            || !rest.iter().all(|candidate| {
8245                candidate.kind() == first.kind()
8246                    && candidate.fq_name() == first.fq_name()
8247                    && candidate.source() == first.source()
8248            })
8249        {
8250            return None;
8251        }
8252        candidates
8253            .iter()
8254            .copied()
8255            .find(|candidate| same_symbol(candidate, target))
8256            .or_else(|| {
8257                candidates.iter().copied().find(|candidate| {
8258                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
8259                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
8260                })
8261            })
8262    }
8263
8264    pub fn alternate_same_fqn_type_declarations(
8265        &self,
8266        analyzer: &CppGraphSource<'_>,
8267        candidates: &[&CodeUnit],
8268        target: &CodeUnit,
8269    ) -> bool {
8270        let Some(first) = candidates.first() else {
8271            return false;
8272        };
8273        let same_api = first.kind() == target.kind()
8274            && first.fq_name() == target.fq_name()
8275            && first.source() == target.source()
8276            && candidates.iter().all(|candidate| {
8277                candidate.kind() == target.kind()
8278                    && candidate.fq_name() == target.fq_name()
8279                    && candidate.source() == target.source()
8280            })
8281            && candidates
8282                .iter()
8283                .any(|candidate| same_symbol(candidate, target))
8284            && candidates
8285                .iter()
8286                .any(|candidate| !same_logical_symbol(candidate, target));
8287        if !same_api {
8288            return false;
8289        }
8290
8291        let requirements = candidates
8292            .iter()
8293            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
8294            .collect::<Vec<_>>();
8295        requirements.len() > 1
8296            && requirements
8297                .iter()
8298                .all(|requirement| !requirement.is_empty())
8299            && requirements.iter().enumerate().all(|(index, left)| {
8300                requirements[index + 1..].iter().all(|right| {
8301                    left.iter().all(|(_, left_guards)| {
8302                        right.iter().all(|(_, right_guards)| {
8303                            merge_preprocessor_guards(left_guards, right_guards).is_none()
8304                        })
8305                    })
8306                })
8307            })
8308    }
8309
8310    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
8311        let mut pending = vec![terms.to_vec()];
8312        while let Some(branch_terms) = pending.pop() {
8313            let mut normalized = Vec::new();
8314            let mut covers_branch = false;
8315            for term in branch_terms {
8316                if term.iter().any(|guard| term.contains(&guard.negated())) {
8317                    continue;
8318                }
8319                if term.is_empty() {
8320                    covers_branch = true;
8321                    break;
8322                }
8323                if !normalized.iter().any(|existing| existing == &term) {
8324                    normalized.push(term);
8325                }
8326            }
8327            if covers_branch {
8328                continue;
8329            }
8330            let Some(split_guard) = normalized
8331                .iter()
8332                .flat_map(|term| term.iter())
8333                .next()
8334                .cloned()
8335            else {
8336                return false;
8337            };
8338            let negated_guard = split_guard.negated();
8339            let mut when_defined = Vec::new();
8340            let mut when_undefined = Vec::new();
8341            for term in normalized {
8342                if term.contains(&negated_guard) {
8343                    // This term cannot hold when `split_guard` is true.
8344                } else if term.contains(&split_guard) {
8345                    let mut reduced = term.clone();
8346                    reduced.remove(&split_guard);
8347                    when_defined.push(reduced);
8348                } else {
8349                    when_defined.push(term.clone());
8350                }
8351                if term.contains(&split_guard) {
8352                    // This term cannot hold when `split_guard` is false.
8353                } else if term.contains(&negated_guard) {
8354                    let mut reduced = term;
8355                    reduced.remove(&negated_guard);
8356                    when_undefined.push(reduced);
8357                } else {
8358                    when_undefined.push(term);
8359                }
8360            }
8361            pending.push(when_defined);
8362            pending.push(when_undefined);
8363        }
8364        true
8365    }
8366
8367    /// The byte range of the one `#if` family with a terminal `#else` that holds
8368    /// every physical declaration of every candidate, or `None` when they do not
8369    /// share one such family.
8370    ///
8371    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
8372    /// whose macros changed between declarations. Require every physical range to
8373    /// belong to one syntax-tree family with a terminal `#else` before the terms
8374    /// can prove branch coverage.
8375    fn declarations_share_exhaustive_conditional_family(
8376        &self,
8377        analyzer: &CppGraphSource<'_>,
8378        candidates: &[&CodeUnit],
8379    ) -> Option<(usize, usize)> {
8380        let mut family_range = None;
8381        for candidate in candidates {
8382            let mut candidate_family = None;
8383            for occurrence in analyzer.declaration_source_occurrences(candidate)? {
8384                let key = occurrence.exhaustive_family?;
8385                if candidate_family.is_some_and(|existing| existing != key) {
8386                    return None;
8387                }
8388                candidate_family = Some(key);
8389            }
8390            let candidate_family = candidate_family?;
8391            if family_range.is_some_and(|existing| existing != candidate_family) {
8392                return None;
8393            }
8394            family_range = Some(candidate_family);
8395        }
8396        family_range
8397    }
8398
8399    pub fn complementary_same_fqn_type_declarations(
8400        &self,
8401        analyzer: &CppGraphSource<'_>,
8402        candidates: &[&CodeUnit],
8403        target: &CodeUnit,
8404    ) -> bool {
8405        if candidates.len() < 2
8406            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
8407            || self
8408                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
8409                .is_none()
8410        {
8411            return false;
8412        }
8413        Self::preprocessor_guard_terms_cover_all_paths(
8414            &self.declaration_family_guard_terms(analyzer, candidates),
8415        )
8416    }
8417
8418    fn declaration_family_guard_terms(
8419        &self,
8420        analyzer: &CppGraphSource<'_>,
8421        candidates: &[&CodeUnit],
8422    ) -> Vec<HashSet<PreprocessorGuard>> {
8423        candidates
8424            .iter()
8425            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
8426            .map(|(_, guards)| guards)
8427            .collect()
8428    }
8429
8430    /// A callable name declared on every branch of one completed `#if`/`#else`
8431    /// family is declared on every configuration path, so a reference below the
8432    /// whole family sees one of the branches whatever the preprocessor decides.
8433    /// Answer the family's end byte: only past `#endif` is every branch's
8434    /// declaration behind the reference.
8435    ///
8436    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
8437    /// and shares both of its primitives. It does not require two distinct
8438    /// `CodeUnit`s: branches that declare the same signature can collapse into
8439    /// one unit carrying one physical range per branch.
8440    ///
8441    /// The branches are alternate spellings of one declaration, never competing
8442    /// declarations, so only the first branch stands for the family. Reporting
8443    /// every branch as visible would turn a name the source declares exactly
8444    /// once into an ambiguity between build configurations.
8445    fn exhaustive_guard_family_activation(
8446        &self,
8447        analyzer: &CppGraphSource<'_>,
8448        candidate: &CodeUnit,
8449        reference: &CallableReferenceContext<'_>,
8450    ) -> Option<usize> {
8451        // Branch coverage says nothing about scope: a block-local declaration
8452        // stays invisible however many branches declare it.
8453        if !analyzer
8454            .declaration_source_occurrences(candidate)?
8455            .iter()
8456            .any(|occurrence| occurrence.fact.callable_activation.is_some())
8457        {
8458            return None;
8459        }
8460        let family = self
8461            .visible_identifier_candidates(reference.file, candidate.identifier())
8462            .filter(|peer| {
8463                peer.kind() == candidate.kind()
8464                    && peer.fq_name() == candidate.fq_name()
8465                    && peer.source() == candidate.source()
8466            })
8467            .collect::<Vec<_>>();
8468        let (_, family_end) =
8469            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
8470        if !Self::preprocessor_guard_terms_cover_all_paths(
8471            &self.declaration_family_guard_terms(analyzer, &family),
8472        ) {
8473            return None;
8474        }
8475        // A reference whose effective context picks one branch already reaches
8476        // that branch through the ordinary same-guard path; the family must not
8477        // resurrect the branch the reference contradicts. The context includes
8478        // the translation unit's stable `__cplusplus` state and build-proven
8479        // defines, not only the reference's source guards: a C file below
8480        // `#ifdef __cplusplus ... #else ... #endif` takes the `#else` arm
8481        // however the family enumerates its branches.
8482        let effective = self.effective_reference_guards(reference.file, reference.guards());
8483        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
8484            .iter()
8485            .any(|(_, guards)| guards_compatible_at_reference(guards, effective.as_ref()))
8486        {
8487            return None;
8488        }
8489        (first_declaration_byte(analyzer, candidate)?
8490            == family
8491                .iter()
8492                .filter_map(|peer| first_declaration_byte(analyzer, peer))
8493                .min()?)
8494        .then_some(family_end)
8495    }
8496
8497    fn type_candidate_preserving_target(
8498        &self,
8499        analyzer: &CppGraphSource<'_>,
8500        visible_from: &ProjectFile,
8501        candidate: &CodeUnit,
8502        target: &CodeUnit,
8503    ) -> Option<CodeUnit> {
8504        let mut current = candidate.clone();
8505        let mut matched_target = same_visible_symbol(&current, target)
8506            || self.compatible_primary_template_redeclarations(&current, target);
8507        let mut seen = HashSet::default();
8508        loop {
8509            if !seen.insert(current.clone()) {
8510                return None;
8511            }
8512            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8513                return matched_target
8514                    .then(|| target.clone())
8515                    .or_else(|| current.is_class().then_some(current));
8516            };
8517            if self.flattened_macro_namespace_alias_target_matches(
8518                analyzer,
8519                visible_from,
8520                &current,
8521                &alias_target,
8522                target,
8523            ) {
8524                return Some(target.clone());
8525            }
8526            if matches!(alias_target, StructuredAliasTarget::Builtin) {
8527                return matched_target
8528                    .then(|| target.clone())
8529                    .or_else(|| current.is_class().then_some(current));
8530            }
8531            // A non-template alias can name a template alias with explicit
8532            // arguments (for example, `using Result = Expected<int>`).  When
8533            // the requested target is that alias's primary declaration, keep
8534            // the primary identity before expanding the RHS arguments.  The
8535            // expansion would otherwise canonicalize through the underlying
8536            // implementation type and lose the target spelling used by the
8537            // forward resolver.
8538            if !self.cpp_template_metadata.contains_key(&current)
8539                && let Some(primary) =
8540                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
8541                && (same_visible_symbol(&primary, target)
8542                    || self.compatible_primary_template_redeclarations(&primary, target))
8543            {
8544                return Some(target.clone());
8545            }
8546            if same_visible_symbol(&current, target) {
8547                return Some(target.clone());
8548            }
8549            if self.cpp_template_metadata.contains_key(&current) {
8550                return None;
8551            }
8552            let Some(next) =
8553                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
8554            else {
8555                return matched_target.then(|| target.clone());
8556            };
8557            current = next;
8558            matched_target |= same_visible_symbol(&current, target)
8559                || self.compatible_primary_template_redeclarations(&current, target);
8560        }
8561    }
8562
8563    fn compatible_primary_template_redeclarations(
8564        &self,
8565        left: &CodeUnit,
8566        right: &CodeUnit,
8567    ) -> bool {
8568        let (Some(left_metadata), Some(right_metadata)) = (
8569            self.cpp_template_metadata.get(left),
8570            self.cpp_template_metadata.get(right),
8571        ) else {
8572            return false;
8573        };
8574        left_metadata.primary_fq_name == right_metadata.primary_fq_name
8575            && left_metadata.is_primary()
8576            && right_metadata.is_primary()
8577            && cpp_reconcile_primary_template_parameters(
8578                &[(left, left_metadata), (right, right_metadata)],
8579                right,
8580            )
8581            .is_some()
8582    }
8583
8584    fn alias_candidate_may_preserve_target(
8585        &self,
8586        analyzer: &CppGraphSource<'_>,
8587        visible_from: &ProjectFile,
8588        candidate: &CodeUnit,
8589        target: &CodeUnit,
8590    ) -> bool {
8591        // A hop that lands on a conditional alias family follows every arm:
8592        // this is a may-reach gate, and the reference's guards choose the arm
8593        // later (#6902).
8594        let mut pending = vec![candidate.clone()];
8595        let mut seen = HashSet::default();
8596        while let Some(current) = pending.pop() {
8597            if same_visible_symbol(&current, target)
8598                || self.compatible_primary_template_redeclarations(&current, target)
8599            {
8600                return true;
8601            }
8602            if self.cpp_template_metadata.contains_key(&current) {
8603                return true;
8604            }
8605            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8606                continue;
8607            };
8608            let StructuredAliasTarget::Named {
8609                components,
8610                global,
8611                arguments,
8612            } = alias_target
8613            else {
8614                continue;
8615            };
8616            if arguments.is_some() || !seen.insert(current.clone()) {
8617                return true;
8618            }
8619            let qualified = components.join("::");
8620            let next = if global {
8621                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8622            } else {
8623                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
8624            };
8625            let Some(next) = next else {
8626                return true;
8627            };
8628            match self.conditional_alias_arms(analyzer, visible_from, &next) {
8629                Some(arms) => pending.extend(arms),
8630                None => pending.push(next),
8631            }
8632        }
8633        false
8634    }
8635
8636    fn alias_candidate_structurally_reaches_target(
8637        &self,
8638        analyzer: &CppGraphSource<'_>,
8639        visible_from: &ProjectFile,
8640        candidate: &CodeUnit,
8641        target: &CodeUnit,
8642    ) -> bool {
8643        // Every arm of a conditional alias family on the chain may reach the
8644        // target; the reference's guards choose among them later (#6902).
8645        let mut pending = vec![candidate.clone()];
8646        let mut seen = HashSet::default();
8647        while let Some(current) = pending.pop() {
8648            if same_visible_symbol(&current, target)
8649                || self.compatible_primary_template_redeclarations(&current, target)
8650                || self
8651                    .cpp_template_metadata
8652                    .get(&current)
8653                    .zip(self.cpp_template_metadata.get(target))
8654                    .is_some_and(|(current, target)| {
8655                        current.primary_fq_name == target.primary_fq_name
8656                    })
8657            {
8658                return true;
8659            }
8660            if !seen.insert(current.clone()) {
8661                continue;
8662            }
8663            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8664                continue;
8665            };
8666            if let StructuredAliasTarget::Named { components, .. } = &alias_target
8667                && components[..components.len().saturating_sub(1)]
8668                    .iter()
8669                    .any(|component| {
8670                        self.type_reference_component_directly_names_target(component, target)
8671                    })
8672                && self.structured_class_alias_path_preserves_target(
8673                    analyzer,
8674                    visible_from,
8675                    &current,
8676                    target,
8677                )
8678            {
8679                return true;
8680            }
8681            if matches!(alias_target, StructuredAliasTarget::Builtin) {
8682                continue;
8683            }
8684            let Some(primary) =
8685                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
8686            else {
8687                continue;
8688            };
8689            match self.conditional_alias_arms(analyzer, visible_from, &primary) {
8690                Some(arms) => pending.extend(arms),
8691                None => pending.push(primary),
8692            }
8693        }
8694        false
8695    }
8696
8697    /// Whether one indexed alias component in a qualified type reference has
8698    /// a structured path to `target`.
8699    ///
8700    /// Alias terminal names such as `type` are deliberately common. A
8701    /// file-wide name set cannot distinguish `Gen<T>::type`, whose indexed RHS
8702    /// names the target, from thousands of unrelated `Traits<T>::type`
8703    /// references. Match every qualified prefix against the alias's canonical
8704    /// scope before following its structured RHS. An unresolved dependent RHS
8705    /// fails this coarse gate only when no other component names the target;
8706    /// references such as `Identity<Target>::type` are admitted by that target
8707    /// component before this helper is called.
8708    pub fn qualified_alias_reference_may_reach_target(
8709        &self,
8710        analyzer: &CppGraphSource<'_>,
8711        file: &ProjectFile,
8712        components: &[String],
8713        global: bool,
8714        target: &CodeUnit,
8715    ) -> bool {
8716        for end in 0..components.len() {
8717            let spelled = &components[..=end];
8718            for candidate in self
8719                .visible_identifier_candidates(file, &components[end])
8720                .filter(|candidate| declared_type_alias(analyzer, candidate))
8721            {
8722                let candidate_components = canonical_cpp_scope_components(candidate);
8723                let shape_matches = if global {
8724                    candidate_components == spelled
8725                } else {
8726                    candidate_components.ends_with(spelled)
8727                };
8728                if shape_matches
8729                    && self.alias_candidate_structurally_reaches_target(
8730                        analyzer, file, candidate, target,
8731                    )
8732                {
8733                    return true;
8734                }
8735            }
8736        }
8737        false
8738    }
8739
8740    /// Every indexed type declaration `raw_name` names when it is written in
8741    /// `declaration`'s namespace: the innermost enclosing namespace that holds
8742    /// the name wins, otherwise the name is looked up unqualified.
8743    fn type_candidates_for_declaration<'b>(
8744        &'b self,
8745        visible_from: &ProjectFile,
8746        declaration: &CodeUnit,
8747        raw_name: &str,
8748    ) -> Vec<&'b CodeUnit> {
8749        let Some(normalized) = normalize_reference_name(raw_name) else {
8750            return Vec::new();
8751        };
8752        if let Some(namespace) = cpp_namespace_for(declaration) {
8753            for prefix in namespace_prefixes(&namespace) {
8754                let qualified = format!("{prefix}::{normalized}");
8755                let candidates = self.type_candidates(visible_from, &qualified);
8756                if !candidates.is_empty() {
8757                    return candidates;
8758                }
8759            }
8760        }
8761        self.type_candidates(visible_from, &normalized)
8762    }
8763
8764    fn resolve_unique_type_for_declaration(
8765        &self,
8766        visible_from: &ProjectFile,
8767        declaration: &CodeUnit,
8768        raw_name: &str,
8769    ) -> Option<CodeUnit> {
8770        unique_logical_type_candidate(self.type_candidates_for_declaration(
8771            visible_from,
8772            declaration,
8773            raw_name,
8774        ))
8775    }
8776
8777    /// The type `raw_name` names at `declaration`, answered with the
8778    /// declaration that defines it.
8779    ///
8780    /// C spells one type twice. `typedef struct T T;` in the header that
8781    /// publishes the handle indexes an incomplete `struct T` beside the
8782    /// complete `struct T { ... }` in the header that declares the members, and
8783    /// both carry the same kind and fully qualified name, so
8784    /// [`logical_type_candidate`] is free to answer with either. mbedtls writes
8785    /// exactly that: `include/mbedtls/ssl.h` forward-declares
8786    /// `mbedtls_ssl_handshake_params` for the pointer member
8787    /// `mbedtls_ssl_handshake_params *MBEDTLS_PRIVATE(handshake)` while
8788    /// `library/ssl_misc.h` defines its fields. A receiver typed from a field
8789    /// declaration is asked for its members on the next link of the chain, and
8790    /// only the definition owns them (#2982).
8791    ///
8792    /// Competing definitions leave nothing to choose between, so keep the
8793    /// logical answer rather than picking one of them.
8794    fn resolve_defining_type_for_declaration(
8795        &self,
8796        analyzer: &CppGraphSource<'_>,
8797        visible_from: &ProjectFile,
8798        declaration: &CodeUnit,
8799        raw_name: &str,
8800    ) -> Option<CodeUnit> {
8801        let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8802        let logical = unique_logical_type_candidate(candidates.clone())?;
8803        let mut defining = candidates.into_iter().filter(|candidate| {
8804            candidate.is_class()
8805                && cpp_class_declaration_strength(analyzer, candidate)
8806                    == CppClassDeclarationStrength::Full
8807        });
8808        match (defining.next(), defining.next()) {
8809            (Some(unique_definition), None) => Some(unique_definition.clone()),
8810            _ => Some(logical),
8811        }
8812    }
8813
8814    pub fn resolves_to_type(
8815        &self,
8816        analyzer: &CppGraphSource<'_>,
8817        file: &ProjectFile,
8818        raw_name: &str,
8819        target: &CodeUnit,
8820    ) -> bool {
8821        let Some(normalized) = normalize_reference_name(raw_name) else {
8822            return false;
8823        };
8824        let candidates = self.type_candidates(file, &normalized);
8825        if candidates.is_empty() {
8826            return self.parser_alias_resolves_to_type(file, raw_name, target);
8827        }
8828        let Some(resolved) =
8829            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8830        else {
8831            return false;
8832        };
8833        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8834    }
8835
8836    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8837        let mut current = alias.clone();
8838        let mut visited = HashSet::default();
8839        while visited.insert(current.clone()) {
8840            let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), &current)?;
8841            let resolved =
8842                self.resolve_type_for_declaration(current.source(), &current, &raw_target)?;
8843            if resolved.is_class() {
8844                return Some(resolved);
8845            }
8846            if !declared_type_alias(&self.cpp_source(), &resolved) {
8847                return None;
8848            }
8849            current = resolved;
8850        }
8851        None
8852    }
8853
8854    /// Whether two callable declarations declare one function.
8855    ///
8856    /// [`same_logical_symbol`] compares the persisted signature strings, which
8857    /// embed each parameter type exactly as it was spelled. A header
8858    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
8859    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
8860    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
8861    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
8862    /// This resolves the written parameter names before comparing them and
8863    /// reports the same answer the language does for the cases it can prove.
8864    ///
8865    /// Everything it cannot prove stays two symbols: a template declaration, a
8866    /// parameter with no comparable shape, a name that resolves on one side
8867    /// only, and an alias chain it cannot follow safely (#2010).
8868    pub fn same_logical_callable(
8869        &self,
8870        analyzer: &CppGraphSource<'_>,
8871        left: &CodeUnit,
8872        right: &CodeUnit,
8873    ) -> bool {
8874        if same_logical_symbol(left, right) {
8875            return true;
8876        }
8877        if left.kind() != right.kind()
8878            || !left.is_callable()
8879            || !right.is_callable()
8880            || left.fq_name() != right.fq_name()
8881        {
8882            return false;
8883        }
8884        let (Some(left_comparable), Some(right_comparable)) = (
8885            self.callable_comparable(analyzer, left),
8886            self.callable_comparable(analyzer, right),
8887        ) else {
8888            return false;
8889        };
8890        // A template declaration and its out-of-line body can also diverge
8891        // outside the parameter list - `template <class T>` against
8892        // `template <typename U>` - and the template head is part of the
8893        // persisted signature. The structured template levels decide whether
8894        // the heads and owner arguments agree, and bind each side's parameter
8895        // names for the parameter comparison below. A template whose levels
8896        // cannot be read keeps string identity.
8897        let binding = match (&left_comparable.template, &right_comparable.template) {
8898            (Some(left_template), Some(right_template))
8899                if left_template.is_templated() || right_template.is_templated() =>
8900            {
8901                match left_template.reconcile(right_template) {
8902                    CppTemplateReconciliation::Equivalent(binding) => binding,
8903                    CppTemplateReconciliation::Distinct
8904                    | CppTemplateReconciliation::Unsupported => return false,
8905                }
8906            }
8907            (Some(_), Some(_)) => CppTemplateBinding::default(),
8908            _ if self.callable_is_template_declaration(analyzer, left)
8909                || self.callable_is_template_declaration(analyzer, right) =>
8910            {
8911                return false;
8912            }
8913            _ => CppTemplateBinding::default(),
8914        };
8915        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
8916        // return type and requires-clause are part of C++ callable identity and
8917        // an out-of-line definition repeats them verbatim, so they must agree
8918        // as written.
8919        if left_comparable.suffix != right_comparable.suffix
8920            || left_comparable.shapes.len() != right_comparable.shapes.len()
8921        {
8922            return false;
8923        }
8924        left_comparable
8925            .shapes
8926            .iter()
8927            .zip(right_comparable.shapes.iter())
8928            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8929                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8930                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8931                    self.comparable_shapes_agree(analyzer, &binding, left_shape, right_shape)
8932                }
8933                // An unstructured parameter records that the reduction failed,
8934                // not that the two spellings mean the same type, so it agrees
8935                // with nothing - including another unstructured parameter.
8936                _ => false,
8937            })
8938    }
8939
8940    /// Whether two complete class definitions in different files define one
8941    /// class.
8942    ///
8943    /// The one-definition rule makes every definition of a class with linkage
8944    /// the same entity, and it requires those definitions to agree token for
8945    /// token ([basic.def.odr]). A shared qualified name is only the spelling of
8946    /// that claim, so it is never enough. The evidence is structural: both
8947    /// definitions are complete, their template levels reconcile (heads, and
8948    /// the arguments that select a specialization), their base specifiers
8949    /// agree, and they declare the same members -- the same kinds, names and
8950    /// signatures, which embed each member's template head as written. Two
8951    /// classes that share a name but differ in any of these stay two classes.
8952    pub fn same_logical_class_definition(
8953        &self,
8954        analyzer: &CppGraphSource<'_>,
8955        left: &CodeUnit,
8956        right: &CodeUnit,
8957    ) -> bool {
8958        if same_symbol(left, right) {
8959            return true;
8960        }
8961        if !left.is_class() || !right.is_class() || left.fq_name() != right.fq_name() {
8962            return false;
8963        }
8964        let key = if left <= right {
8965            (left.clone(), right.clone())
8966        } else {
8967            (right.clone(), left.clone())
8968        };
8969        visibility_memo_value(&self.class_definition_agreements, &self.reads, key, || {
8970            self.class_definitions_agree(analyzer, left, right)
8971        })
8972    }
8973
8974    fn class_definitions_agree(
8975        &self,
8976        analyzer: &CppGraphSource<'_>,
8977        left: &CodeUnit,
8978        right: &CodeUnit,
8979    ) -> bool {
8980        let complete_definition = |class: &CodeUnit| {
8981            let mut facts = analyzer
8982                .declaration_source_properties(class)?
8983                .into_iter()
8984                .filter(|fact| fact.class_strength == CppClassDeclarationStrength::Full);
8985            let fact = facts.next()?;
8986            // Two complete definitions in one file are a conditional family
8987            // this comparison does not choose between.
8988            facts.next().is_none().then_some(fact)
8989        };
8990        let (Some(left_fact), Some(right_fact)) =
8991            (complete_definition(left), complete_definition(right))
8992        else {
8993            return false;
8994        };
8995        if left_fact.bases != right_fact.bases {
8996            return false;
8997        }
8998        let (Some(left_template), Some(right_template)) = (
8999            self.declaration_template_context(left, left_fact.declaration),
9000            self.declaration_template_context(right, right_fact.declaration),
9001        ) else {
9002            return false;
9003        };
9004        if !matches!(
9005            left_template.reconcile(&right_template),
9006            CppTemplateReconciliation::Equivalent(_)
9007        ) {
9008            return false;
9009        }
9010        let members = |class: &CodeUnit| {
9011            let mut members = analyzer
9012                .direct_children(class)
9013                .into_iter()
9014                .filter(|member| member.source() == class.source())
9015                .map(|member| {
9016                    (
9017                        member.kind(),
9018                        member.identifier().to_owned(),
9019                        member.signature().map(str::to_owned),
9020                    )
9021                })
9022                .collect::<Vec<_>>();
9023            members.sort();
9024            members
9025        };
9026        members(left) == members(right)
9027    }
9028
9029    /// Whether `file` sees a definition of `class` through an identical
9030    /// definition in another header.
9031    ///
9032    /// The include graph relates a header to the translation units that
9033    /// include it. When a class is defined identically in two headers, a
9034    /// translation unit that includes only one copy still defines members of
9035    /// the class the other copy declares: the two definitions are one class
9036    /// ([`Self::same_logical_class_definition`]). Each source declaration
9037    /// keeps its own identity; only the relation crosses.
9038    pub fn class_definition_bridges_to_file(
9039        &self,
9040        analyzer: &CppGraphSource<'_>,
9041        class: &CodeUnit,
9042        file: &ProjectFile,
9043        header_body_related: &dyn Fn(&ProjectFile, &ProjectFile) -> bool,
9044    ) -> bool {
9045        class.is_class()
9046            && class.source() != file
9047            && crate::identity::cpp_source_path_is_header(class.source())
9048            && analyzer
9049                .index
9050                .definitions_by_structured_name(class.fq(), Language::Cpp)
9051                .into_iter()
9052                .any(|copy| {
9053                    // The copy's own header holds the copy's declarations,
9054                    // which keep their own provenance; only files beyond it
9055                    // are reached through the copy.
9056                    copy.is_class()
9057                        && copy.source() != class.source()
9058                        && copy.source() != file
9059                        && crate::identity::cpp_source_path_is_header(copy.source())
9060                        && header_body_related(copy.source(), file)
9061                        && self.same_logical_class_definition(analyzer, class, &copy)
9062                })
9063    }
9064
9065    /// Whether the header member declaration `declaration` reaches `body`
9066    /// through an identical definition of its class
9067    /// ([`Self::class_definition_bridges_to_file`]).
9068    pub fn member_declaration_bridges_to_body(
9069        &self,
9070        analyzer: &CppGraphSource<'_>,
9071        declaration: &CodeUnit,
9072        body: &CodeUnit,
9073        header_body_related: &dyn Fn(&ProjectFile, &ProjectFile) -> bool,
9074    ) -> bool {
9075        declaration.is_callable()
9076            && self
9077                .cpp
9078                .structural_parent_of(declaration)
9079                .is_some_and(|owner| {
9080                    self.class_definition_bridges_to_file(
9081                        analyzer,
9082                        &owner,
9083                        body.source(),
9084                        header_body_related,
9085                    )
9086                })
9087    }
9088
9089    /// Compare two parameter shapes node by node with an explicit paired stack.
9090    ///
9091    /// Shape variants and cv-qualifiers must agree exactly at every level; only
9092    /// the named leaves may be spelled differently, and they agree when they
9093    /// resolve to one type declaration.
9094    fn comparable_shapes_agree(
9095        &self,
9096        analyzer: &CppGraphSource<'_>,
9097        binding: &CppTemplateBinding,
9098        left: &CppComparableParameter,
9099        right: &CppComparableParameter,
9100    ) -> bool {
9101        let mut stack = vec![(left.root(), right.root())];
9102        while let Some((left_index, right_index)) = stack.pop() {
9103            match (left.node(left_index), right.node(right_index)) {
9104                (
9105                    CppComparableNode::Named {
9106                        name: left_name,
9107                        primitive: left_primitive,
9108                        konst: left_konst,
9109                        volatil: left_volatil,
9110                    },
9111                    CppComparableNode::Named {
9112                        name: right_name,
9113                        primitive: right_primitive,
9114                        konst: right_konst,
9115                        volatil: right_volatil,
9116                    },
9117                ) => {
9118                    if left_konst != right_konst
9119                        || left_volatil != right_volatil
9120                        || left_primitive != right_primitive
9121                        || !self.comparable_names_agree(
9122                            analyzer,
9123                            binding,
9124                            left_name,
9125                            right_name,
9126                            *left_primitive,
9127                        )
9128                    {
9129                        return false;
9130                    }
9131                }
9132                (
9133                    CppComparableNode::Pointer {
9134                        inner: left_inner,
9135                        konst: left_konst,
9136                        volatil: left_volatil,
9137                    },
9138                    CppComparableNode::Pointer {
9139                        inner: right_inner,
9140                        konst: right_konst,
9141                        volatil: right_volatil,
9142                    },
9143                ) => {
9144                    if left_konst != right_konst || left_volatil != right_volatil {
9145                        return false;
9146                    }
9147                    stack.push((*left_inner, *right_inner));
9148                }
9149                (
9150                    CppComparableNode::Reference { inner: left_inner },
9151                    CppComparableNode::Reference { inner: right_inner },
9152                )
9153                | (
9154                    CppComparableNode::Array { inner: left_inner },
9155                    CppComparableNode::Array { inner: right_inner },
9156                ) => stack.push((*left_inner, *right_inner)),
9157                (
9158                    CppComparableNode::Generic {
9159                        base: left_base,
9160                        arguments: left_arguments,
9161                    },
9162                    CppComparableNode::Generic {
9163                        base: right_base,
9164                        arguments: right_arguments,
9165                    },
9166                ) => {
9167                    if left_arguments.len() != right_arguments.len() {
9168                        return false;
9169                    }
9170                    stack.push((*left_base, *right_base));
9171                    stack.extend(
9172                        left_arguments.iter().zip(right_arguments.iter()).map(
9173                            |(left_argument, right_argument)| (*left_argument, *right_argument),
9174                        ),
9175                    );
9176                }
9177                _ => return false,
9178            }
9179        }
9180        true
9181    }
9182
9183    /// Whether two written type names denote one type.
9184    ///
9185    /// A primitive denotes the same type in every scope, so its recorded
9186    /// lexical scope is noise and its spelling decides. A nominal name is
9187    /// resolved on each side independently: two resolved names agree when they
9188    /// reach one type declaration, and two unresolved names agree only on
9189    /// exact agreement of what was written, which is no weaker than the
9190    /// whole-signature string equality this comparison replaces. Resolution on
9191    /// one side only is evidence of difference, never of agreement.
9192    ///
9193    /// A template parameter hides every outer name it spells, so a path that
9194    /// starts with one is decided by `binding` alone: both sides must start
9195    /// with the parameters the reconciled heads pair, and agree on the rest
9196    /// of the path (`typename T::value_type`).
9197    fn comparable_names_agree(
9198        &self,
9199        analyzer: &CppGraphSource<'_>,
9200        binding: &CppTemplateBinding,
9201        left: &StructuredTypeName,
9202        right: &StructuredTypeName,
9203        primitive: bool,
9204    ) -> bool {
9205        if primitive {
9206            return left.path() == right.path();
9207        }
9208        if !binding.is_empty() {
9209            let (Some(left_head), Some(right_head)) = (left.path().first(), right.path().first())
9210            else {
9211                return false;
9212            };
9213            match (
9214                binding.is_left_parameter(left_head),
9215                binding.is_right_parameter(right_head),
9216            ) {
9217                (true, true) => {
9218                    return !left.is_absolute()
9219                        && !right.is_absolute()
9220                        && binding.binds(right_head, left_head)
9221                        && left.path()[1..] == right.path()[1..];
9222                }
9223                (false, false) => {}
9224                _ => return false,
9225            }
9226        }
9227        match (
9228            self.comparable_name_terminal(analyzer, left),
9229            self.comparable_name_terminal(analyzer, right),
9230        ) {
9231            (Some(left_terminal), Some(right_terminal)) => {
9232                same_logical_symbol(&left_terminal, &right_terminal)
9233            }
9234            (None, None) => {
9235                left.path() == right.path() && left.is_absolute() == right.is_absolute()
9236            }
9237            _ => false,
9238        }
9239    }
9240
9241    /// The class declaration a written type name denotes, or `None` when the
9242    /// workspace cannot prove one.
9243    ///
9244    /// The lookup is a closure-independent lexical-scope prefix walk over the
9245    /// stored C++ identifier index rather than a visibility lookup: a body's
9246    /// `.cpp` is almost never in the reference file's include closure. Any name
9247    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
9248    /// cannot invent a type the compiler could not see; `using`-directives are
9249    /// not modelled, and a name that needs one stays unresolved.
9250    fn comparable_name_terminal(
9251        &self,
9252        analyzer: &CppGraphSource<'_>,
9253        name: &StructuredTypeName,
9254    ) -> Option<CodeUnit> {
9255        let mut current = self.comparable_name_declaration(analyzer, name)?;
9256        let mut visited = HashSet::default();
9257        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
9258            // The alias question is asked before the class question, and
9259            // through `declared_type_alias` rather than `is_type_alias`,
9260            // because extraction records `using A8 = A7;` as a *Class* unit
9261            // whose signature is the alias declaration. Reading the kind first
9262            // would end the chase on the alias itself and report an alias
9263            // spelling and its underlying class as two types (#2010).
9264            if !declared_type_alias(analyzer, &current) {
9265                return current.is_class().then_some(current);
9266            }
9267            if !visited.insert(current.clone()) {
9268                return None;
9269            }
9270            let facts = analyzer.declaration_source_properties(&current)?;
9271            if facts.iter().any(|fact| fact.adds_indirection) {
9272                return None;
9273            }
9274            let raw_target = cpp_canonical_alias_target_text(analyzer, &current)?;
9275            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
9276        }
9277        None
9278    }
9279
9280    /// The declaration one alias hop lands on: the type `raw_target` names,
9281    /// looked up from the alias declaration's own enclosing namespace.
9282    ///
9283    /// The hop takes the same closure-independent prefix walk the first lookup
9284    /// took, and deliberately not `resolve_type_for_declaration`: that one
9285    /// answers out of the `VisibilityIndex`, which is rooted at the reference
9286    /// file, while the alias declaration this hop starts from is reached
9287    /// through the workspace definition index and its file need not be in that
9288    /// root's include closure - where the visibility lookup answers nothing and
9289    /// the chase would stop on the alias itself (#2010).
9290    fn comparable_alias_target(
9291        &self,
9292        analyzer: &CppGraphSource<'_>,
9293        alias: &CodeUnit,
9294        raw_target: &str,
9295    ) -> Option<CodeUnit> {
9296        // `raw_target` is the alias declaration's written type text, so it is a
9297        // plain `::`-joined qualified-id: the same domain the shared symbol-path
9298        // parser reads, and the same leading `::` that marks an absolute name
9299        // everywhere else this crate normalizes a reference.
9300        let absolute = raw_target.trim_start().starts_with("::");
9301        let normalized = normalize_reference_name(raw_target)?;
9302        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9303            brokk_bifrost_core::analyzer::Language::Cpp,
9304            &normalized,
9305        );
9306        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
9307            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9308                brokk_bifrost_core::analyzer::Language::Cpp,
9309                &namespace,
9310            )
9311        });
9312        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
9313        self.comparable_name_declaration(analyzer, &name)
9314    }
9315
9316    /// The one type declaration `name` names, by enclosing scope, innermost
9317    /// first.
9318    ///
9319    /// The first prefix depth that names anything decides: an inner scope hides
9320    /// an outer one, so a match there is the answer even when an outer scope
9321    /// also declares the name. Several logically distinct declarations at that
9322    /// depth are an ambiguity this comparison must not guess at.
9323    fn comparable_name_declaration(
9324        &self,
9325        analyzer: &CppGraphSource<'_>,
9326        name: &StructuredTypeName,
9327    ) -> Option<CodeUnit> {
9328        visibility_memo_value(
9329            &self.comparable_name_declarations,
9330            &self.reads,
9331            name.clone(),
9332            || {
9333                let interner = segment_interner();
9334                let identifier = name.path().last()?;
9335                let candidates_by_identifier =
9336                    self.cpp.visibility_identifier_candidates(identifier);
9337                let first_depth = if name.is_absolute() {
9338                    0
9339                } else {
9340                    name.lexical_scope().len()
9341                };
9342                let mut resolved = None;
9343                for depth in (0..=first_depth).rev() {
9344                    let mut structured = FqName::new();
9345                    for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
9346                        structured.push(interner.intern(component, SegmentKind::Unknown));
9347                    }
9348                    let mut candidates = candidates_by_identifier
9349                        .iter()
9350                        .filter(|unit| unit.fq().same_segment_texts(&structured))
9351                        .filter(|unit| {
9352                            unit.kind() == CodeUnitType::Class
9353                                || declared_type_alias(analyzer, unit)
9354                        })
9355                        .cloned();
9356                    let Some(first) = candidates.next() else {
9357                        continue;
9358                    };
9359                    resolved = candidates
9360                        .all(|unit| same_logical_symbol(&unit, &first))
9361                        .then_some(first);
9362                    break;
9363                }
9364                resolved
9365            },
9366        )
9367    }
9368
9369    /// The comparison inputs of one callable declaration, extracted once.
9370    ///
9371    /// The comparison itself runs only when two candidates share kind and fully
9372    /// qualified name but not signature, which is rare; re-reading the same
9373    /// declaration for every pair in a candidate set is not.
9374    fn callable_comparable(
9375        &self,
9376        analyzer: &CppGraphSource<'_>,
9377        unit: &CodeUnit,
9378    ) -> Option<Arc<ExtractedComparable>> {
9379        visibility_memo_value(
9380            &self.callable_comparables,
9381            &self.reads,
9382            unit.clone(),
9383            || {
9384                self.extract_callable_comparable(analyzer, unit)
9385                    .map(Arc::new)
9386            },
9387        )
9388    }
9389
9390    fn extract_callable_comparable(
9391        &self,
9392        analyzer: &CppGraphSource<'_>,
9393        unit: &CodeUnit,
9394    ) -> Option<ExtractedComparable> {
9395        let fact = analyzer
9396            .declaration_source_properties(unit)?
9397            .into_iter()
9398            .find(|fact| fact.callable_comparable_shapes.is_some())?;
9399        let template = self.declaration_template_context(unit, fact.declaration);
9400        Some(ExtractedComparable {
9401            shapes: fact.callable_comparable_shapes?,
9402            suffix: fact.callable_identity_suffix?,
9403            template,
9404        })
9405    }
9406
9407    /// The template levels of one source declaration of `unit`, read from
9408    /// the file's prepared syntax at the declaration's exact occurrence.
9409    pub fn declaration_template_context(
9410        &self,
9411        unit: &CodeUnit,
9412        declaration: SourceDeclarationId,
9413    ) -> Option<CppTemplateContext> {
9414        let facts = self
9415            .cpp
9416            .declaration_source_facts(self.token, unit.source())?;
9417        let occurrence = facts.source.declaration(declaration).occurrence;
9418        let range = facts.source.occurrence(occurrence).range;
9419        let syntax = self.cpp.prepared_syntax(self.token, unit.source())?;
9420        let mut node = node_for_exact_range(syntax.tree().root_node(), &range)?;
9421        // An occurrence may span the template wrapper; the context starts at
9422        // the declaration it wraps.
9423        while node.kind() == "template_declaration" {
9424            node = node.named_child(node.named_child_count().checked_sub(1)?)?;
9425        }
9426        cpp_declaration_template_context(node, syntax.source())
9427    }
9428
9429    pub fn canonical_type_for_reference(
9430        &self,
9431        file: &ProjectFile,
9432        raw_name: &str,
9433    ) -> Option<CodeUnit> {
9434        let resolved = self.resolve_type(file, raw_name)?;
9435        self.alias_target(&resolved).or(Some(resolved))
9436    }
9437
9438    pub fn parser_alias_resolves_to_type(
9439        &self,
9440        file: &ProjectFile,
9441        raw_name: &str,
9442        target: &CodeUnit,
9443    ) -> bool {
9444        let Some(alias_name) = normalize_reference_name(raw_name) else {
9445            return false;
9446        };
9447        any_visible_source_file_matches(&self.visible_source_files_by_root, file, |source_file| {
9448            self.file_alias_matches(self.cpp, source_file, &alias_name, target)
9449        })
9450    }
9451
9452    fn file_alias_matches(
9453        &self,
9454        cpp: &dyn CppSource,
9455        file: &ProjectFile,
9456        alias_name: &str,
9457        target: &CodeUnit,
9458    ) -> bool {
9459        self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
9460            cell.get()
9461                .expect("published canonical aliases")
9462                .iter()
9463                .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
9464        })
9465    }
9466
9467    fn canonical_alias_cell(
9468        &self,
9469        cpp: &dyn CppSource,
9470        file: &ProjectFile,
9471    ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
9472        try_visibility_memo_value(&self.alias_cells, &self.reads, file.clone(), || {
9473            let aliases = aliases_from_canonical_source(cpp, self.token, file).ok_or(())?;
9474            self.parser_alias_source_parses
9475                .fetch_add(1, Ordering::Relaxed);
9476            #[cfg(any(test, feature = "test-support"))]
9477            {
9478                *self
9479                    .alias_source_parse_counts
9480                    .lock()
9481                    .expect("alias source parse count lock")
9482                    .entry(file.clone())
9483                    .or_default() += 1;
9484            }
9485            let cell = Arc::new(OnceLock::new());
9486            cell.get_or_init(|| aliases.into_boxed_slice());
9487            Ok::<_, ()>(cell)
9488        })
9489        .ok()
9490    }
9491
9492    #[cfg(any(test, feature = "test-support"))]
9493    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
9494        self.visible_source_files_by_root
9495            .get(file)
9496            .cloned()
9497            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
9498    }
9499
9500    #[cfg(any(test, feature = "test-support"))]
9501    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
9502        self.alias_source_parse_counts
9503            .lock()
9504            .expect("alias source parse count lock")
9505            .get(file)
9506            .copied()
9507            .unwrap_or(0)
9508    }
9509
9510    #[cfg(any(test, feature = "test-support"))]
9511    pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
9512        self.parser_alias_fallback_files.load(Ordering::Relaxed)
9513    }
9514
9515    pub fn resolve_named(
9516        &self,
9517        file: &ProjectFile,
9518        raw_name: &str,
9519        kind: TargetKind,
9520    ) -> Option<CodeUnit> {
9521        let normalized = normalize_reference_name(raw_name)?;
9522        self.named_candidates_for_normalized(file, &normalized, kind)
9523            .into_iter()
9524            .next()
9525            .cloned()
9526    }
9527
9528    pub fn contains_named_symbol(
9529        &self,
9530        file: &ProjectFile,
9531        raw_name: &str,
9532        kind: TargetKind,
9533        target: &CodeUnit,
9534    ) -> bool {
9535        let Some(normalized) = normalize_reference_name(raw_name) else {
9536            return false;
9537        };
9538        self.named_candidates_for_normalized(file, &normalized, kind)
9539            .into_iter()
9540            .any(|unit| {
9541                matches_kind_for_lookup(unit, kind)
9542                    && reference_matches_unit(&normalized, unit)
9543                    && same_visible_symbol(unit, target)
9544            })
9545    }
9546
9547    pub fn named_candidates(
9548        &self,
9549        file: &ProjectFile,
9550        raw_name: &str,
9551        kind: TargetKind,
9552    ) -> Vec<CodeUnit> {
9553        let Some(normalized) = normalize_reference_name(raw_name) else {
9554            return Vec::new();
9555        };
9556        self.named_candidates_for_normalized(file, &normalized, kind)
9557            .into_iter()
9558            .cloned()
9559            .collect()
9560    }
9561
9562    pub fn resolve_known_non_target(
9563        &self,
9564        file: &ProjectFile,
9565        raw_name: &str,
9566        kind: TargetKind,
9567        target: &CodeUnit,
9568    ) -> bool {
9569        let Some(normalized) = normalize_reference_name(raw_name) else {
9570            return false;
9571        };
9572        normalized.contains("::")
9573            && self
9574                .named_candidates_for_normalized(file, &normalized, kind)
9575                .into_iter()
9576                .any(|unit| {
9577                    matches_kind_for_lookup(unit, kind)
9578                        && reference_matches_unit(&normalized, unit)
9579                        && !same_visible_symbol(unit, target)
9580                })
9581    }
9582
9583    pub fn resolve_call_return_binding(
9584        &self,
9585        analyzer: &CppGraphSource<'_>,
9586        file: &ProjectFile,
9587        raw_name: &str,
9588        arity: usize,
9589        lexical_namespace: Option<&str>,
9590        direct_type: Option<&CodeUnit>,
9591    ) -> Option<CppScanBinding> {
9592        let normalized = normalize_reference_name(raw_name)?;
9593        let mut candidates = Vec::new();
9594        for function in
9595            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
9596        {
9597            if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
9598                && !direct_type.is_some_and(|direct_type| {
9599                    self.callable_is_constructor_declaration(analyzer, function)
9600                        && type_owner_of(analyzer, function)
9601                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
9602                })
9603            {
9604                candidates.push(function.clone());
9605            }
9606        }
9607        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
9608        unanimous_return_binding(analyzer, self, file, &candidates)
9609    }
9610
9611    pub fn resolve_call_return_binding_without_arity(
9612        &self,
9613        analyzer: &CppGraphSource<'_>,
9614        file: &ProjectFile,
9615        raw_name: &str,
9616        lexical_namespace: Option<&str>,
9617        direct_type: Option<&CodeUnit>,
9618    ) -> (bool, Option<CppScanBinding>) {
9619        let Some(normalized) = normalize_reference_name(raw_name) else {
9620            return (false, None);
9621        };
9622        let mut candidates = self
9623            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
9624            .into_iter()
9625            .filter(|function| {
9626                function.is_function()
9627                    && !direct_type.is_some_and(|direct_type| {
9628                        self.callable_is_constructor_declaration(analyzer, function)
9629                            && type_owner_of(analyzer, function)
9630                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
9631                    })
9632            })
9633            .cloned()
9634            .collect::<Vec<_>>();
9635        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
9636        let has_candidates = !candidates.is_empty();
9637        (
9638            has_candidates,
9639            unanimous_return_binding(analyzer, self, file, &candidates),
9640        )
9641    }
9642
9643    pub fn visible_identifier_candidates<'b>(
9644        &'b self,
9645        file: &ProjectFile,
9646        identifier: &str,
9647    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
9648        self.visible_by_identifier
9649            .get(file)
9650            .and_then(|by_name| by_name.get(identifier))
9651            .into_iter()
9652            .flatten()
9653    }
9654
9655    /// Whether a grammar component is the direct spelling of `target`.
9656    ///
9657    /// Concrete template specializations are indexed with their arguments in
9658    /// `identifier`, while a reference's name component contains only the
9659    /// primary name. Argument matching remains the later resolver's job.
9660    pub fn type_reference_component_directly_names_target(
9661        &self,
9662        component: &str,
9663        target: &CodeUnit,
9664    ) -> bool {
9665        component == target.identifier()
9666            || self
9667                .cpp_template_metadata
9668                .get(target)
9669                .is_some_and(|metadata| component == metadata.primary_name)
9670    }
9671
9672    /// Return terminal reference names that can denote `target` from `file`.
9673    ///
9674    /// The indexed candidate table covers ordinary declarations and aliases;
9675    /// Parser-only aliases are tested lazily when their spelling is actually
9676    /// encountered in a scanned type node. Enumerating them here would parse
9677    /// every source in the include closure even when the target's direct name
9678    /// is the only spelling present in the file.
9679    pub fn visible_type_reference_component_names_for_target(
9680        &self,
9681        analyzer: &CppGraphSource<'_>,
9682        file: &ProjectFile,
9683        target: &CodeUnit,
9684    ) -> HashSet<String> {
9685        let mut names = HashSet::from_iter([target.identifier().to_string()]);
9686        if let Some(metadata) = self.cpp_template_metadata.get(target) {
9687            names.insert(metadata.primary_name.clone());
9688        }
9689
9690        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
9691            for (identifier, candidates) in by_identifier {
9692                if candidates.iter().any(|candidate| {
9693                    (candidate.is_class()
9694                        && (same_visible_symbol(candidate, target)
9695                            || self.compatible_primary_template_redeclarations(candidate, target)))
9696                        || (declared_type_alias(analyzer, candidate)
9697                            && self.alias_candidate_structurally_reaches_target(
9698                                analyzer, file, candidate, target,
9699                            ))
9700                }) {
9701                    names.insert(identifier.clone());
9702                }
9703            }
9704        }
9705
9706        names
9707    }
9708
9709    pub fn indexed_structural_class_scope(
9710        &self,
9711        file: &ProjectFile,
9712        class: Node<'_>,
9713        source: &str,
9714    ) -> Option<Vec<String>> {
9715        let key = (file.clone(), class.start_byte(), class.end_byte());
9716        visibility_memo_value(
9717            &self.indexed_structural_class_scopes,
9718            &self.reads,
9719            key,
9720            || {
9721                let name = class.child_by_field_name("name")?;
9722                let identifier = if name.kind() == "template_type" {
9723                    node_text(name.child_by_field_name("name")?, source).to_string()
9724                } else {
9725                    let mut components = Vec::new();
9726                    append_cpp_name_components(name, source, &mut components)?;
9727                    components.last()?.clone()
9728                };
9729                let visible = self
9730                    .visible_identifier_candidates(file, &identifier)
9731                    .cloned()
9732                    .collect::<Vec<_>>();
9733                let mut visible = visible;
9734                for candidate in
9735                    self.visible_by_file
9736                        .get(file)
9737                        .into_iter()
9738                        .flatten()
9739                        .filter(|candidate| {
9740                            self.cpp_template_metadata
9741                                .get(candidate)
9742                                .is_some_and(|metadata| metadata.primary_name == identifier)
9743                        })
9744                {
9745                    if !visible
9746                        .iter()
9747                        .any(|existing| same_logical_symbol(existing, candidate))
9748                    {
9749                        visible.push(candidate.clone());
9750                    }
9751                }
9752                // Built once per call rather than per candidate; `cpp_source` rebuilds
9753                // the five-field source from the same `self.cpp` on every call.
9754                let cpp_source = self.cpp_source();
9755                let candidates = visible
9756                    .iter()
9757                    .filter(|candidate| {
9758                        candidate.source() == file
9759                            && candidate.is_class()
9760                            && !declared_type_alias(&cpp_source, candidate)
9761                            && self.cpp.ranges(candidate).iter().any(|range| {
9762                                range.start_byte <= class.start_byte()
9763                                    && class.end_byte() <= range.end_byte
9764                            })
9765                    })
9766                    .collect::<Vec<_>>();
9767                let owner = if name.kind() == "template_type" {
9768                    let expected = normalize_cpp_whitespace(node_text(name, source));
9769                    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
9770                    let exact = candidates
9771                        .iter()
9772                        .copied()
9773                        .filter(|candidate| {
9774                            candidate
9775                                .fq()
9776                                .segments()
9777                                .iter()
9778                                .rev()
9779                                .find_map(|&segment| {
9780                                    let (text, kind) = interner.resolve(segment);
9781                                    matches!(
9782                                    kind,
9783                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
9784                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
9785                                )
9786                                    .then_some(text)
9787                                })
9788                                .is_some_and(|text| text == expected)
9789                        })
9790                        .collect::<Vec<_>>();
9791                    unique_logical_type_candidate(exact)
9792                        .or_else(|| unique_logical_type_candidate(candidates.clone()))?
9793                } else {
9794                    unique_logical_type_candidate(candidates)?
9795                };
9796                Some(canonical_cpp_scope_components(&owner))
9797            },
9798        )
9799    }
9800
9801    pub fn indexed_enclosing_owner_scope(
9802        &self,
9803        analyzer: &CppGraphSource<'_>,
9804        file: &ProjectFile,
9805        node: Node<'_>,
9806    ) -> Option<Vec<String>> {
9807        let anchor = std::iter::successors(Some(node), |current| current.parent())
9808            .find(|current| {
9809                matches!(
9810                    current.kind(),
9811                    "function_definition"
9812                        | "class_specifier"
9813                        | "struct_specifier"
9814                        | "union_specifier"
9815                )
9816            })
9817            .unwrap_or(node);
9818        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9819        visibility_memo_value(
9820            &self.indexed_enclosing_owner_scopes,
9821            &self.reads,
9822            key,
9823            || {
9824                let range = Range {
9825                    start_byte: node.start_byte(),
9826                    end_byte: node.end_byte(),
9827                    start_line: node.start_position().row,
9828                    end_line: node.end_position().row,
9829                };
9830                let start = analyzer.enclosing_code_unit(file, &range)?;
9831                let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9832                    start,
9833                    |unit| self.cached_precise_parent_of(analyzer, unit),
9834                )
9835                .find(|unit| {
9836                    unit.is_class()
9837                        && !analyzer
9838                            .type_alias_provider()
9839                            .is_some_and(|provider| provider.is_type_alias(unit))
9840                })?;
9841                Some(canonical_cpp_scope_components(&owner))
9842            },
9843        )
9844    }
9845
9846    fn cached_precise_parent_of(
9847        &self,
9848        analyzer: &CppGraphSource<'_>,
9849        code_unit: &CodeUnit,
9850    ) -> Option<CodeUnit> {
9851        visibility_memo_value(
9852            &self.precise_parent_cache,
9853            &self.reads,
9854            code_unit.clone(),
9855            || precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit),
9856        )
9857    }
9858
9859    pub fn callable_is_constructor_declaration(
9860        &self,
9861        analyzer: &CppGraphSource<'_>,
9862        candidate: &CodeUnit,
9863    ) -> bool {
9864        if !candidate.is_function() {
9865            return false;
9866        }
9867        if analyzer
9868            .declaration_source_properties(candidate)
9869            .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9870        {
9871            return true;
9872        }
9873        let metadata = analyzer.signature_metadata(candidate);
9874        !metadata.is_empty()
9875            && metadata
9876                .iter()
9877                .all(|signature| signature.return_type_text().is_none())
9878    }
9879
9880    /// Whether a callable declaration is a class-template deduction guide.
9881    ///
9882    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
9883    /// type field whose function declarator owns a trailing return type. This
9884    /// structured shape distinguishes a guide from both a constructor (no
9885    /// trailing return) and an ordinary trailing-return function (an `auto`
9886    /// type field).
9887    pub fn callable_is_deduction_guide_declaration(
9888        &self,
9889        analyzer: &CppGraphSource<'_>,
9890        candidate: &CodeUnit,
9891    ) -> bool {
9892        callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_deduction_guide)
9893    }
9894
9895    /// Whether a callable occurrence is directly wrapped by a C++ template
9896    /// declaration. This deliberately inspects declaration syntax instead of
9897    /// inferring template status from the rendered signature.
9898    pub fn callable_is_template_declaration(
9899        &self,
9900        analyzer: &CppGraphSource<'_>,
9901        candidate: &CodeUnit,
9902    ) -> bool {
9903        callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_template)
9904    }
9905
9906    pub fn type_name_candidates<'b>(
9907        &'b self,
9908        file: &ProjectFile,
9909        normalized: &str,
9910    ) -> Vec<&'b CodeUnit> {
9911        self.candidate_units(file, normalized, TargetKind::Type)
9912    }
9913
9914    pub fn visible_members_for_owner_name<'b>(
9915        &'b self,
9916        file: &ProjectFile,
9917        owner: &CodeUnit,
9918        name: &str,
9919    ) -> Vec<&'b CodeUnit> {
9920        self.visible_identifier_candidates(file, name)
9921            .filter(|unit| {
9922                // Structured owner pop on the unit's own `fq()` (shared with
9923                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
9924                // string.
9925                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9926                    .is_some_and(|parent| parent == owner.fq_name())
9927            })
9928            .collect()
9929    }
9930
9931    pub fn visible_member_for_owner_name(
9932        &self,
9933        file: &ProjectFile,
9934        owner: &CodeUnit,
9935        name: &str,
9936    ) -> VisibleMemberResolution {
9937        let candidates = self.visible_members_for_owner_name(file, owner, name);
9938        let mut callables = Vec::new();
9939        let mut non_callable = None;
9940        for candidate in candidates {
9941            if candidate.is_function() {
9942                callables.push(candidate.clone());
9943            } else if non_callable.is_none() {
9944                non_callable = Some(candidate.clone());
9945            }
9946        }
9947        match (callables.is_empty(), non_callable) {
9948            (false, None) => VisibleMemberResolution::Callable(callables),
9949            (true, Some(_)) => VisibleMemberResolution::NonCallable,
9950            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9951            (true, None) => VisibleMemberResolution::Missing,
9952        }
9953    }
9954
9955    fn field_declared_type_fact(
9956        &self,
9957        analyzer: &CppGraphSource<'_>,
9958        field: &CodeUnit,
9959    ) -> Option<DeclaredFieldTypeFact> {
9960        visibility_memo_value(&self.field_type_facts, &self.reads, field.clone(), || {
9961            decode_field_declared_type_fact(analyzer, field)
9962        })
9963    }
9964
9965    fn structured_alias_target(
9966        &self,
9967        analyzer: &CppGraphSource<'_>,
9968        unit: &CodeUnit,
9969    ) -> Option<StructuredAliasTarget> {
9970        visibility_memo_value(
9971            &self.structured_alias_targets,
9972            &self.reads,
9973            unit.clone(),
9974            || decode_structured_alias_target(analyzer, unit),
9975        )
9976    }
9977
9978    pub fn type_candidates<'b>(
9979        &'b self,
9980        file: &ProjectFile,
9981        normalized: &str,
9982    ) -> Vec<&'b CodeUnit> {
9983        // A bare name reaches a function-local class only through block-scope
9984        // lookup at a reference inside its function
9985        // (`function_local_type_resolution`), never through the visible-name
9986        // table. Its canonical qualified spelling still matches below.
9987        let qualified = normalized.contains("::");
9988        let mut candidates = self
9989            .candidate_units(file, normalized, TargetKind::Type)
9990            .into_iter()
9991            .filter(|unit| {
9992                unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9993            })
9994            .filter(|unit| qualified || !is_function_local_unit(unit))
9995            .collect::<Vec<_>>();
9996        dedup_unit_refs(&mut candidates);
9997        candidates
9998    }
9999
10000    pub fn named_candidates_for_normalized<'b>(
10001        &'b self,
10002        file: &ProjectFile,
10003        normalized: &str,
10004        kind: TargetKind,
10005    ) -> Vec<&'b CodeUnit> {
10006        let mut candidates = self
10007            .candidate_units(file, normalized, kind)
10008            .into_iter()
10009            .filter(|unit| {
10010                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
10011            })
10012            .collect::<Vec<_>>();
10013        dedup_unit_refs(&mut candidates);
10014        candidates
10015    }
10016
10017    pub fn candidate_units<'b>(
10018        &'b self,
10019        file: &ProjectFile,
10020        normalized: &str,
10021        kind: TargetKind,
10022    ) -> Vec<&'b CodeUnit> {
10023        if normalized.contains("::") {
10024            // `normalized` comes from `normalize_cpp_reference_text`, which
10025            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
10026            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
10027            // kept intact by the shared splitter's operator merge — the same
10028            // domain `cpp_reference_fqn_candidates` below already parses with
10029            // the shared splitter. Re-tokenizing and taking the last segment
10030            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
10031            // scan exactly.
10032            let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10033                brokk_bifrost_core::analyzer::Language::Cpp,
10034                normalized,
10035            );
10036            let Some(identifier) = parts.last() else {
10037                return Vec::new();
10038            };
10039            let fqns = cpp_reference_fqn_candidates_from_parts(&parts, kind);
10040            return self
10041                .visible_identifier_candidates(file, identifier)
10042                .filter(|unit| {
10043                    #[cfg(any(test, feature = "test-support"))]
10044                    self.qualified_candidate_inspections
10045                        .fetch_add(1, Ordering::Relaxed);
10046                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
10047                        || canonical_cpp_name_matches(unit, normalized)
10048                })
10049                .collect();
10050        }
10051        self.visible_identifier_candidates(file, normalized)
10052            .collect()
10053    }
10054
10055    #[cfg(any(test, feature = "test-support"))]
10056    pub fn reset_qualified_candidate_inspections(&self) {
10057        self.qualified_candidate_inspections
10058            .store(0, Ordering::Relaxed);
10059    }
10060
10061    #[cfg(any(test, feature = "test-support"))]
10062    pub fn qualified_candidate_inspections(&self) -> usize {
10063        self.qualified_candidate_inspections.load(Ordering::Relaxed)
10064    }
10065
10066    #[cfg(any(test, feature = "test-support"))]
10067    pub fn visibility_identifier_lookup_count(&self) -> usize {
10068        self.visibility_identifier_lookup_count
10069    }
10070
10071    #[cfg(any(test, feature = "test-support"))]
10072    pub fn visibility_identifier_batch_count(&self) -> usize {
10073        self.visibility_identifier_batch_count
10074    }
10075
10076    #[cfg(any(test, feature = "test-support"))]
10077    pub fn reset_target_preserving_type_resolution_count(&self) {
10078        self.target_preserving_type_resolution_count
10079            .store(0, Ordering::Relaxed);
10080    }
10081
10082    #[cfg(any(test, feature = "test-support"))]
10083    pub fn target_preserving_type_resolution_count(&self) -> usize {
10084        self.target_preserving_type_resolution_count
10085            .load(Ordering::Relaxed)
10086    }
10087
10088    #[cfg(any(test, feature = "test-support"))]
10089    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
10090        self.visible_parser_alias_name_set_build_count
10091            .load(Ordering::Relaxed)
10092    }
10093}
10094
10095fn callable_declaration_has_fact(
10096    analyzer: &CppGraphSource<'_>,
10097    candidate: &CodeUnit,
10098    matches: impl Fn(&brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact) -> bool,
10099) -> bool {
10100    candidate.is_function()
10101        && analyzer
10102            .declaration_source_properties(candidate)
10103            .is_some_and(|facts| facts.iter().any(matches))
10104}
10105
10106#[derive(Default)]
10107struct IncludeGraph {
10108    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
10109}
10110
10111impl IncludeGraph {
10112    fn extend_with<F>(
10113        &mut self,
10114        root: &ProjectFile,
10115        cancellation: Option<&CancellationToken>,
10116        targets_for: &mut F,
10117    ) where
10118        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
10119    {
10120        let mut stack = vec![root.clone()];
10121        while let Some(file) = stack.pop() {
10122            if cancellation.is_some_and(CancellationToken::is_cancelled) {
10123                break;
10124            }
10125            if self.targets_by_file.contains_key(&file) {
10126                continue;
10127            }
10128            let targets = targets_for(&file);
10129            stack.extend(targets.iter().cloned());
10130            self.targets_by_file.insert(file, targets);
10131        }
10132    }
10133
10134    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
10135        self.targets_by_file.keys()
10136    }
10137
10138    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
10139        self.targets_by_file
10140            .get(file)
10141            .map(Vec::as_slice)
10142            .unwrap_or_default()
10143    }
10144
10145    fn reachable_files(
10146        &self,
10147        root: &ProjectFile,
10148        cancellation: Option<&CancellationToken>,
10149    ) -> HashSet<ProjectFile> {
10150        let mut pending = vec![root.clone()];
10151        let mut visited = HashSet::default();
10152        while let Some(file) = pending.pop() {
10153            if cancellation.is_some_and(CancellationToken::is_cancelled) {
10154                break;
10155            }
10156            if visited.insert(file.clone()) {
10157                pending.extend(self.targets(&file).iter().cloned());
10158            }
10159        }
10160        visited
10161    }
10162}
10163
10164fn build_bounded_visible_declarations(
10165    cpp: &dyn CppSource,
10166    token: QueryToken<'_>,
10167    analyzer: &CppGraphSource<'_>,
10168    roots: &HashSet<ProjectFile>,
10169    visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10170    cancellation: Option<&CancellationToken>,
10171    stats: &mut BoundedVisibilityStats,
10172) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
10173    let mut candidates_by_identifier = HashMap::default();
10174    let mut declarations_by_source_and_reading: HashMap<
10175        (ProjectFile, bool),
10176        BoundedVisibilityDeclarations,
10177    > = HashMap::default();
10178    let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
10179    roots
10180        .iter()
10181        .map(|root| {
10182            let reading_is_c = analyzer.reference_uses_c_semantics(root);
10183            let root_declarations = declarations_by_source_and_reading
10184                .entry((root.clone(), reading_is_c))
10185                .or_insert_with(|| {
10186                    bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
10187                });
10188            let mut visible = root_declarations
10189                .all
10190                .iter()
10191                .cloned()
10192                .collect::<HashSet<_>>();
10193            let mut pending_names = HashSet::default();
10194            if let Some(prepared) = cpp.prepared_syntax(token, root) {
10195                // One cursor for the whole file walk: `named_child(index)`
10196                // re-steps the sibling list from the first child on every
10197                // access, which made this whole-file scan quadratic in the
10198                // fan-out of each node (#3097).
10199                let mut cursor = prepared.tree().walk();
10200                let mut pending_nodes = vec![prepared.tree().root_node()];
10201                while let Some(node) = pending_nodes.pop() {
10202                    if matches!(
10203                        node.kind(),
10204                        "identifier"
10205                            | "type_identifier"
10206                            | "field_identifier"
10207                            | "namespace_identifier"
10208                    ) {
10209                        pending_names.insert(node_text(node, prepared.source()).to_string());
10210                    }
10211                    if node.kind() == "preproc_arg" {
10212                        for reference in
10213                            object_macro_replacement_type_references(node, prepared.source())
10214                        {
10215                            pending_names.extend(reference.components);
10216                        }
10217                    }
10218                    pending_nodes.extend(node.named_children(&mut cursor));
10219                }
10220            }
10221            stats.root_names += pending_names.len();
10222            let mut completed_names = HashSet::default();
10223            while !pending_names.is_empty() {
10224                stats.rounds += 1;
10225                let round_names = std::mem::take(&mut pending_names);
10226                let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
10227                    HashMap::default();
10228                let mut identifiers = Vec::new();
10229                for identifier in round_names {
10230                    if !completed_names.insert(identifier.clone())
10231                        || cancellation.is_some_and(CancellationToken::is_cancelled)
10232                    {
10233                        continue;
10234                    }
10235                    identifiers.push(identifier);
10236                }
10237                let missing_identifiers = identifiers
10238                    .iter()
10239                    .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
10240                    .cloned()
10241                    .collect::<HashSet<_>>();
10242                if !missing_identifiers.is_empty() {
10243                    let lookup_started = Instant::now();
10244                    let mut candidates = cpp
10245                        .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
10246                    stats.lookup_elapsed += lookup_started.elapsed();
10247                    stats.identifier_lookups += missing_identifiers.len();
10248                    stats.identifier_batches += 1;
10249                    if cancellation.is_some_and(CancellationToken::is_cancelled) {
10250                        break;
10251                    }
10252                    for identifier in missing_identifiers {
10253                        let units = candidates.remove(&identifier).unwrap_or_default();
10254                        let candidate_count = units.len();
10255                        let candidate_sources = units
10256                            .into_iter()
10257                            .map(|unit| unit.source().clone())
10258                            .collect::<HashSet<_>>();
10259                        candidates_by_identifier
10260                            .insert(identifier, (candidate_sources, candidate_count));
10261                    }
10262                }
10263                for identifier in identifiers {
10264                    let (candidate_sources, candidate_count) = candidates_by_identifier
10265                        .get(&identifier)
10266                        .expect("every missing identifier was inserted after the batch lookup");
10267                    stats.candidate_units += *candidate_count;
10268                    for source in candidate_sources.iter().cloned() {
10269                        if source != *root
10270                            && visible_sources
10271                                .get(root)
10272                                .is_some_and(|files| files.contains(&source))
10273                        {
10274                            requested_names_by_source
10275                                .entry(source)
10276                                .or_default()
10277                                .insert(identifier.clone());
10278                        }
10279                    }
10280                }
10281                stats.candidate_sources += requested_names_by_source.len();
10282                for (source, requested_names) in requested_names_by_source {
10283                    let declarations = declarations_by_source_and_reading
10284                        .entry((source.clone(), reading_is_c))
10285                        .or_insert_with(|| {
10286                            bounded_visibility_declarations(
10287                                cpp,
10288                                analyzer,
10289                                &source,
10290                                reading_is_c,
10291                                stats,
10292                            )
10293                        });
10294                    let mut selected = HashSet::default();
10295                    for name in requested_names {
10296                        if let Some(units) = declarations.by_identifier.get(&name) {
10297                            selected.extend(units.iter().cloned());
10298                        }
10299                    }
10300                    for unit in selected {
10301                        stats.selected_units += 1;
10302                        let dependency_names = dependency_names_by_unit
10303                            .entry(unit.clone())
10304                            .or_insert_with(|| {
10305                                let facts_started = Instant::now();
10306                                let mut dependency_names = HashSet::default();
10307                                if let Some(facts) = analyzer.declaration_source_properties(&unit) {
10308                                    for fact in facts {
10309                                        for name in fact.dependency_type_names {
10310                                            stats.dependency_source_names += 1;
10311                                            #[cfg(any(test, feature = "test-support"))]
10312                                            BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
10313                                                .with(|count| count.set(count.get() + 1));
10314                                            dependency_names.insert(name);
10315                                        }
10316                                    }
10317                                    stats.dependency_source_elapsed += facts_started.elapsed();
10318                                }
10319                                dependency_names
10320                            });
10321                        for name in dependency_names.iter() {
10322                            if !completed_names.contains(name) && pending_names.insert(name.clone())
10323                            {
10324                                stats.dependency_names += 1;
10325                            }
10326                        }
10327                        visible.insert(unit);
10328                    }
10329                }
10330            }
10331            (root.clone(), visible)
10332        })
10333        .collect()
10334}
10335
10336struct BoundedVisibilityDeclarations {
10337    all: Vec<CodeUnit>,
10338    by_identifier: HashMap<String, Vec<CodeUnit>>,
10339}
10340
10341fn bounded_visibility_declarations(
10342    cpp: &dyn CppSource,
10343    analyzer: &CppGraphSource<'_>,
10344    file: &ProjectFile,
10345    c_semantics: bool,
10346    stats: &mut BoundedVisibilityStats,
10347) -> BoundedVisibilityDeclarations {
10348    let declarations_started = Instant::now();
10349    let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
10350        .into_iter()
10351        .collect::<Vec<_>>();
10352    stats.declaration_elapsed += declarations_started.elapsed();
10353    stats.declaration_reads += 1;
10354    stats.declaration_units += all.len();
10355
10356    let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10357    for unit in &all {
10358        by_identifier
10359            .entry(unit.identifier().to_string())
10360            .or_default()
10361            .push(unit.clone());
10362        if unit.is_class()
10363            && let Some(metadata) = cpp.template_metadata(unit)
10364            && metadata.primary_name != unit.identifier()
10365        {
10366            by_identifier
10367                .entry(metadata.primary_name)
10368                .or_default()
10369                .push(unit.clone());
10370        }
10371    }
10372    BoundedVisibilityDeclarations { all, by_identifier }
10373}
10374
10375#[derive(Default)]
10376struct BoundedVisibilityStats {
10377    rounds: usize,
10378    root_names: usize,
10379    identifier_lookups: usize,
10380    identifier_batches: usize,
10381    candidate_units: usize,
10382    candidate_sources: usize,
10383    declaration_reads: usize,
10384    declaration_units: usize,
10385    selected_units: usize,
10386    dependency_source_names: usize,
10387    dependency_names: usize,
10388    lookup_elapsed: Duration,
10389    declaration_elapsed: Duration,
10390    dependency_source_elapsed: Duration,
10391}
10392
10393fn bounded_visibility_declarations_in_reading(
10394    analyzer: &CppGraphSource<'_>,
10395    file: &ProjectFile,
10396    c_semantics: bool,
10397) -> BTreeSet<CodeUnit> {
10398    #[cfg(any(test, feature = "test-support"))]
10399    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
10400    analyzer.declarations_in_reading(file, c_semantics)
10401}
10402
10403#[cfg(any(test, feature = "test-support"))]
10404pub fn reset_bounded_visibility_declaration_read_count_for_test() {
10405    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
10406}
10407
10408#[cfg(any(test, feature = "test-support"))]
10409pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
10410    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
10411}
10412
10413#[cfg(any(test, feature = "test-support"))]
10414pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
10415    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
10416}
10417
10418#[cfg(any(test, feature = "test-support"))]
10419pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
10420    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
10421}
10422
10423/// Admit the class that an out-of-line definition proves is in scope.
10424///
10425/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
10426/// names a class-like entity in that file's scope: a member declaration can
10427/// live in a file other than its class's only when it is written out of line.
10428/// A file a build concatenates rather than compiles carries no `#include` edge
10429/// to the header declaring `Owner` -- google/wuffs
10430/// `internal/cgen/auxiliary/image.cc` defines
10431/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
10432/// every unqualified member and constructor reference in it had no candidate at
10433/// all (#1832).
10434///
10435/// The evidence is the indexed declaration's own owner name, taken from its
10436/// `FqName`, so this stays a structured answer rather than a text fallback.
10437/// Only an owner the file cannot already see is admitted: that is what keeps a
10438/// header declaring its own class from additionally seeing every same-named
10439/// class in the workspace, and it makes the pass free for the ordinary file
10440/// whose owners are all visible.
10441#[derive(Default)]
10442struct OutOfLineOwnerBindingStats {
10443    unseen_owners: usize,
10444    definition_lookups: usize,
10445    admitted: usize,
10446}
10447
10448fn extend_with_out_of_line_owner_bindings(
10449    cpp: &dyn CppSource,
10450    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
10451) -> OutOfLineOwnerBindingStats {
10452    let mut stats = OutOfLineOwnerBindingStats::default();
10453    for (file, visible) in visible_by_file.iter_mut() {
10454        // The include-closure walk seeds every root with its own declarations,
10455        // so the file's members are already here; re-reading them from the
10456        // analyzer would pay for the same declaration set twice.
10457        let mut unseen_owners: HashSet<String> = visible
10458            .iter()
10459            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
10460            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
10461            .collect();
10462        if unseen_owners.is_empty() {
10463            continue;
10464        }
10465        for unit in visible.iter().filter(|unit| unit.is_class()) {
10466            unseen_owners.remove(&unit.fq_name());
10467        }
10468        stats.unseen_owners += unseen_owners.len();
10469        stats.definition_lookups += unseen_owners.len();
10470        let admitted = unseen_owners
10471            .iter()
10472            .flat_map(|owner| cpp.definitions(owner))
10473            .filter(CodeUnit::is_class)
10474            .collect::<Vec<_>>();
10475        stats.admitted += admitted.len();
10476        visible.extend(admitted);
10477    }
10478    stats
10479}
10480
10481pub enum VisibleMemberResolution {
10482    Callable(Vec<CodeUnit>),
10483    NonCallable,
10484    AmbiguousKind,
10485    Missing,
10486}
10487
10488#[derive(Clone)]
10489pub enum EnclosingMemberOwnerResolution {
10490    Owner(CodeUnit),
10491    Ambiguous,
10492    Missing,
10493}
10494
10495pub fn resolve_declaring_member_owner(
10496    analyzer: &CppGraphSource<'_>,
10497    visibility: &VisibilityIndex<'_>,
10498    file: &ProjectFile,
10499    receiver_owner: &CodeUnit,
10500    member_name: &str,
10501) -> EnclosingMemberOwnerResolution {
10502    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10503        return EnclosingMemberOwnerResolution::Missing;
10504    };
10505    let Some(receiver_owner) =
10506        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
10507    else {
10508        return EnclosingMemberOwnerResolution::Ambiguous;
10509    };
10510    let resolve_level = |frontier: &[CodeUnit]| {
10511        let mut member_owners = Vec::new();
10512        for raw_owner in frontier {
10513            let Some(owner) =
10514                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
10515            else {
10516                return EnclosingMemberOwnerResolution::Ambiguous;
10517            };
10518            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
10519                // A type nested in an owner shares the owner's member-name
10520                // index, but it cannot be the value receiver of `owner.name`.
10521                // Keep value members here so an anonymous aggregate field
10522                // does not become ambiguous with its promoted receiver type.
10523                if !member.is_field() && !member.is_function() {
10524                    continue;
10525                }
10526                let Some(member_owner) = type_owner_of(analyzer, member) else {
10527                    return EnclosingMemberOwnerResolution::Ambiguous;
10528                };
10529                if !member_owners
10530                    .iter()
10531                    .any(|existing| same_visible_symbol(existing, &member_owner))
10532                {
10533                    member_owners.push(member_owner);
10534                }
10535            }
10536        }
10537        match member_owners.len() {
10538            0 => EnclosingMemberOwnerResolution::Missing,
10539            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
10540            _ => EnclosingMemberOwnerResolution::Ambiguous,
10541        }
10542    };
10543    // The first declaration on each structured base path hides deeper names,
10544    // regardless of whether its callable overload is applicable at a particular
10545    // call site. Applicability is checked only after this owner is established.
10546    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
10547    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
10548        return direct;
10549    }
10550    let mut stack = hierarchy
10551        .get_direct_ancestors(&receiver_owner)
10552        .definite()
10553        .to_vec();
10554    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
10555    let mut path_matches = Vec::new();
10556    while let Some(raw_owner) = stack.pop() {
10557        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
10558        else {
10559            return EnclosingMemberOwnerResolution::Ambiguous;
10560        };
10561        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
10562        // Propagate at most two occurrences of each owner: that preserves the distinction
10563        // between one and multiple resolving base paths without exponential diamond walks.
10564        let propagated = propagated_counts.entry(owner.clone()).or_default();
10565        if *propagated == 2 {
10566            continue;
10567        }
10568        *propagated += 1;
10569        match resolve_level(std::slice::from_ref(&owner)) {
10570            EnclosingMemberOwnerResolution::Owner(owner) => {
10571                path_matches.push(owner);
10572                if path_matches.len() == 2 {
10573                    return EnclosingMemberOwnerResolution::Ambiguous;
10574                }
10575            }
10576            EnclosingMemberOwnerResolution::Ambiguous => {
10577                return EnclosingMemberOwnerResolution::Ambiguous;
10578            }
10579            EnclosingMemberOwnerResolution::Missing => {
10580                stack.extend(hierarchy.get_direct_ancestors(&owner).definite().to_vec());
10581            }
10582        }
10583    }
10584    match path_matches.len() {
10585        0 => EnclosingMemberOwnerResolution::Missing,
10586        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
10587        _ => unreachable!("base-path matches are capped at one before returning"),
10588    }
10589}
10590
10591/// Resolve the declaring owner of a callable after applying a member
10592/// `using <Base>::<member>;` declaration to one exact call arity.
10593///
10594/// Ordinary member lookup is intentionally name-based: the first class that
10595/// declares a name hides the same name on deeper bases. A member
10596/// using-declaration is the one exception. When none of the declarations on
10597/// that first owner accepts the call arity, it can reintroduce an applicable
10598/// overload from the named base. If a declaration on the first owner does
10599/// accept the arity, argument types would be needed to choose between it and
10600/// a same-arity introduced overload, so this resolver conservatively keeps the
10601/// ordinary owner (#1835/#1843).
10602///
10603/// The caller supplies ordinary name-based owner resolution so a file scan can
10604/// reuse its existing owner cache before applying this callable-only exception.
10605pub fn resolve_declaring_callable_owner(
10606    analyzer: &CppGraphSource<'_>,
10607    visibility: &VisibilityIndex<'_>,
10608    file: &ProjectFile,
10609    ordinary: EnclosingMemberOwnerResolution,
10610    member_name: &str,
10611    call_arity: usize,
10612) -> EnclosingMemberOwnerResolution {
10613    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
10614        return ordinary;
10615    };
10616    if visibility
10617        .visible_members_for_owner_name(file, ordinary_owner, member_name)
10618        .into_iter()
10619        .any(|unit| {
10620            unit.is_function()
10621                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
10622        })
10623    {
10624        return ordinary;
10625    }
10626
10627    let mut pending = match member_using_declaration_bases(
10628        analyzer,
10629        visibility,
10630        file,
10631        ordinary_owner,
10632        member_name,
10633    ) {
10634        Ok(bases) => bases,
10635        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10636    };
10637    let mut visited = HashSet::default();
10638    let mut introduced_owners = Vec::new();
10639    while let Some(owner) = pending.pop() {
10640        if !visited.insert(owner.clone()) {
10641            continue;
10642        }
10643        let accepts_arity = visibility
10644            .visible_members_for_owner_name(file, &owner, member_name)
10645            .into_iter()
10646            .any(|unit| {
10647                unit.is_function()
10648                    && cpp_callable_arity(analyzer, unit)
10649                        .is_none_or(|known| known.accepts(call_arity))
10650            });
10651        if accepts_arity {
10652            if !introduced_owners
10653                .iter()
10654                .any(|existing| same_visible_symbol(existing, &owner))
10655            {
10656                introduced_owners.push(owner);
10657            }
10658            continue;
10659        }
10660        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10661            Ok(bases) => pending.extend(bases),
10662            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10663        }
10664    }
10665    match introduced_owners.as_slice() {
10666        [] => ordinary,
10667        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10668        _ => EnclosingMemberOwnerResolution::Ambiguous,
10669    }
10670}
10671
10672fn member_using_declaration_bases(
10673    analyzer: &CppGraphSource<'_>,
10674    visibility: &VisibilityIndex<'_>,
10675    file: &ProjectFile,
10676    owner: &CodeUnit,
10677    member_name: &str,
10678) -> Result<Vec<CodeUnit>, ()> {
10679    let Some(facts) = analyzer.declaration_source_properties(owner) else {
10680        return Err(());
10681    };
10682    let scopes: Vec<_> = facts
10683        .iter()
10684        .flat_map(|fact| &fact.member_usings)
10685        .filter(|using| using.member == member_name)
10686        .map(|using| using.scope.join("::"))
10687        .collect();
10688    if scopes.is_empty() {
10689        return Ok(Vec::new());
10690    }
10691    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10692        return Ok(Vec::new());
10693    };
10694    let mut bases = Vec::new();
10695    for raw_ancestor in hierarchy.get_ancestors(owner).definite().to_vec() {
10696        let Some(ancestor) =
10697            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10698        else {
10699            return Err(());
10700        };
10701        let qualified = cpp_name_for(&ancestor);
10702        if scopes
10703            .iter()
10704            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10705            && !bases
10706                .iter()
10707                .any(|existing| same_visible_symbol(existing, &ancestor))
10708        {
10709            bases.push(ancestor);
10710        }
10711    }
10712    Ok(bases)
10713}
10714
10715pub fn lexical_component_tiers<'a>(
10716    components: &'a [String],
10717    global: bool,
10718    lexical_scope: &'a [String],
10719) -> impl Iterator<Item = Vec<String>> + 'a {
10720    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10721    (0..=first_prefix_len).rev().map(move |prefix_len| {
10722        let mut qualified = Vec::with_capacity(prefix_len + components.len());
10723        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10724        qualified.extend_from_slice(components);
10725        qualified
10726    })
10727}
10728
10729pub fn build_visible_identifier_index(
10730    analyzer: &CppGraphSource<'_>,
10731    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10732    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10733    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10734) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10735    let mut out = HashMap::default();
10736    for (file, visible) in visible_by_file {
10737        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10738        for unit in visible {
10739            if unit.is_field()
10740                && !visible_source_files_by_root
10741                    .get(file)
10742                    .is_some_and(|sources| sources.contains(unit.source()))
10743                && cpp_global_field_has_internal_linkage_cached(
10744                    analyzer,
10745                    global_field_internal_linkage,
10746                    unit,
10747                )
10748            {
10749                continue;
10750            }
10751            by_identifier
10752                .entry(unit.identifier().to_string())
10753                .or_default()
10754                .push(unit.clone());
10755        }
10756        for units in by_identifier.values_mut() {
10757            sort_lookup_units(units);
10758            units.dedup();
10759        }
10760        out.insert(file.clone(), by_identifier);
10761    }
10762    out
10763}
10764
10765fn sort_lookup_units(units: &mut [CodeUnit]) {
10766    units.sort_by(|left, right| {
10767        left.fq_name()
10768            .cmp(&right.fq_name())
10769            .then_with(|| left.signature().cmp(&right.signature()))
10770            .then_with(|| left.source().cmp(right.source()))
10771            .then_with(|| left.kind().cmp(&right.kind()))
10772            .then_with(|| {
10773                left.package_segment_count()
10774                    .cmp(&right.package_segment_count())
10775            })
10776            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10777            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10778    });
10779}
10780
10781fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10782    let interner = segment_interner();
10783    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10784        let (left_text, left_kind) = interner.resolve(left_id);
10785        let (right_text, right_kind) = interner.resolve(right_id);
10786        let order = left_text
10787            .cmp(right_text)
10788            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10789        if order != CmpOrdering::Equal {
10790            return order;
10791        }
10792    }
10793    left.len().cmp(&right.len())
10794}
10795
10796const fn segment_kind_order(kind: SegmentKind) -> u8 {
10797    match kind {
10798        SegmentKind::Path => 0,
10799        SegmentKind::Package => 1,
10800        SegmentKind::Type => 2,
10801        SegmentKind::Companion => 3,
10802        SegmentKind::Nested => 4,
10803        SegmentKind::Member => 5,
10804        SegmentKind::Unknown => 6,
10805    }
10806}
10807
10808fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10809    *units = brokk_bifrost_core::analyzer::usages::inverted_edges::dedup_preserving_order(
10810        std::mem::take(units),
10811    );
10812}
10813
10814pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10815    // Same domain as `candidate_units` above: `reference` is a plain
10816    // `::`-joined qualified-id with operator tokens kept intact by the shared
10817    // splitter's operator merge.
10818    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10819        brokk_bifrost_core::analyzer::Language::Cpp,
10820        reference,
10821    );
10822    cpp_reference_fqn_candidates_from_parts(&parts, kind)
10823}
10824
10825fn cpp_reference_fqn_candidates_from_parts(parts: &[String], kind: TargetKind) -> Vec<String> {
10826    if parts.is_empty() {
10827        return Vec::new();
10828    }
10829
10830    let mut candidates = Vec::new();
10831    for package_len in 0..parts.len() {
10832        let package = parts[..package_len].join("::");
10833        let rest = &parts[package_len..];
10834        if rest.is_empty() {
10835            continue;
10836        }
10837        match kind {
10838            TargetKind::Type | TargetKind::Constructor => {
10839                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10840                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10841            }
10842            TargetKind::FreeFunction
10843            | TargetKind::Method
10844            | TargetKind::GlobalField
10845            | TargetKind::MemberField
10846            | TargetKind::Macro => {
10847                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10848                if rest.len() > 1 {
10849                    let owner = rest[..rest.len() - 1].join("$");
10850                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10851                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
10852                }
10853            }
10854        }
10855    }
10856    candidates
10857}
10858
10859fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10860    let fqn = if package.is_empty() {
10861        short.to_string()
10862    } else {
10863        format!("{package}.{short}")
10864    };
10865    if !out.contains(&fqn) {
10866        out.push(fqn);
10867    }
10868}
10869
10870pub fn infer_cpp_initializer_type(
10871    analyzer: &CppGraphSource<'_>,
10872    visibility: &VisibilityIndex<'_>,
10873    file: &ProjectFile,
10874    source: &str,
10875    node: Node<'_>,
10876) -> Option<CodeUnit> {
10877    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10878        .and_then(|binding| binding.unit)
10879}
10880
10881pub fn infer_cpp_initializer_binding(
10882    analyzer: &CppGraphSource<'_>,
10883    visibility: &VisibilityIndex<'_>,
10884    file: &ProjectFile,
10885    source: &str,
10886    node: Node<'_>,
10887    receiver_resolver: Option<&ReceiverResolver<'_>>,
10888) -> Option<CppScanBinding> {
10889    match node.kind() {
10890        "new_expression" => {
10891            let type_node = cpp_new_expression_type_node(node)?;
10892            let type_text = node_text(type_node, source);
10893            let name = normalize_cpp_type_name(type_text);
10894            Some(CppScanBinding::from_type_name(
10895                name.clone(),
10896                visibility.resolve_type(file, &name),
10897                1,
10898            ))
10899        }
10900        "call_expression" => node.child_by_field_name("function").and_then(|function| {
10901            // `a().b()` and `p->b()` invoke a member on a receiver *value*. The
10902            // callee's source text is an expression, not a name, and every name
10903            // lookup below normalizes a reference by truncating at the first
10904            // `(`: `first().second` would read as `first`, so the chained call
10905            // would take the type of `first()` instead of the type of
10906            // `first().second()` (#2178). Only the member path can answer for
10907            // this shape, so route to it from the callee's node kind.
10908            if function.kind() == "field_expression" {
10909                let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10910                return resolve_field_method_call_return_binding(
10911                    analyzer,
10912                    visibility,
10913                    file,
10914                    source,
10915                    function,
10916                    arity,
10917                    receiver_resolver,
10918                );
10919            }
10920            let function_text = node_text(function, source);
10921            let direct_type_binding = visibility
10922                .resolve_type(file, function_text)
10923                .map(|unit| CppScanBinding::from_unit(unit, 0));
10924            if function.kind() == "template_function" && direct_type_binding.is_some() {
10925                let lexical_namespace = enclosing_namespace_context(node, source);
10926                let arity = visibility.call_arity_evidence(file, node, source).exact();
10927                if let Some(arity) = arity
10928                    && let Some(binding) = visibility.resolve_call_return_binding(
10929                        analyzer,
10930                        file,
10931                        function_text,
10932                        arity,
10933                        lexical_namespace.as_deref(),
10934                        direct_type_binding
10935                            .as_ref()
10936                            .and_then(|binding| binding.unit.as_ref()),
10937                    )
10938                {
10939                    return Some(binding);
10940                }
10941                let (has_callable, callable_binding) = visibility
10942                    .resolve_call_return_binding_without_arity(
10943                        analyzer,
10944                        file,
10945                        function_text,
10946                        lexical_namespace.as_deref(),
10947                        direct_type_binding
10948                            .as_ref()
10949                            .and_then(|binding| binding.unit.as_ref()),
10950                    );
10951                if let Some(binding) = callable_binding {
10952                    return Some(binding);
10953                }
10954                if has_callable {
10955                    return None;
10956                }
10957                return direct_type_binding;
10958            }
10959            // Only the return-typed branches need the argument count. An
10960            // unknown arity leaves them out, exactly as in the template arm
10961            // above, and still constructs the direct type: `File(getPath())`
10962            // names `File` whether or not `getPath()`'s expansion is provable.
10963            let arity = visibility.call_arity_evidence(file, node, source).exact();
10964            if let Some(arity) = arity {
10965                let direct_type_binding_for_call = direct_type_binding.clone();
10966                if let Some(binding) = resolve_static_method_call_return_binding(
10967                    analyzer, visibility, file, source, function, arity,
10968                )
10969                .or_else(|| {
10970                    // An applicable free function supplies the receiver value
10971                    // before an unrelated visible type with the same terminal
10972                    // name. The direct type still excludes its own constructor
10973                    // declaration below and remains the construction fallback.
10974                    visibility.resolve_call_return_binding(
10975                        analyzer,
10976                        file,
10977                        function_text,
10978                        arity,
10979                        enclosing_namespace_context(node, source).as_deref(),
10980                        direct_type_binding_for_call
10981                            .as_ref()
10982                            .and_then(|binding| binding.unit.as_ref()),
10983                    )
10984                }) {
10985                    return Some(binding);
10986                }
10987            }
10988            direct_type_binding
10989        }),
10990        _ => None,
10991    }
10992}
10993
10994/// Return the type specifier carried by a C++ `new_expression`.
10995///
10996/// The grammar names this child with the `type` field. If recovery omits that
10997/// field, recover only from type-specifier nodes retained in the parse tree;
10998/// skip placement arguments, the array declarator, and initializer arguments.
10999pub fn cpp_new_expression_type_node(expression: Node<'_>) -> Option<Node<'_>> {
11000    debug_assert_eq!(expression.kind(), "new_expression");
11001    if let Some(type_node) = expression.child_by_field_name("type") {
11002        return Some(type_node);
11003    }
11004
11005    let mut pending = Vec::new();
11006    let mut cursor = expression.walk();
11007    pending.extend(
11008        expression
11009            .named_children(&mut cursor)
11010            .filter(|child| {
11011                !matches!(
11012                    child.kind(),
11013                    "argument_list" | "initializer_list" | "new_declarator"
11014                )
11015            })
11016            .collect::<Vec<_>>()
11017            .into_iter()
11018            .rev(),
11019    );
11020    while let Some(node) = pending.pop() {
11021        if is_cpp_type_specifier_node(node) {
11022            return Some(node);
11023        }
11024        if node.kind() == "ERROR" {
11025            let mut cursor = node.walk();
11026            pending.extend(
11027                node.named_children(&mut cursor)
11028                    .collect::<Vec<_>>()
11029                    .into_iter()
11030                    .rev(),
11031            );
11032        }
11033    }
11034    None
11035}
11036
11037fn is_cpp_type_specifier_node(node: Node<'_>) -> bool {
11038    matches!(
11039        node.kind(),
11040        "class_specifier"
11041            | "decltype"
11042            | "dependent_type"
11043            | "enum_specifier"
11044            | "placeholder_type_specifier"
11045            | "primitive_type"
11046            | "qualified_identifier"
11047            | "sized_type_specifier"
11048            | "struct_specifier"
11049            | "template_type"
11050            | "type_identifier"
11051            | "union_specifier"
11052    )
11053}
11054
11055fn resolve_static_method_call_return_binding(
11056    analyzer: &CppGraphSource<'_>,
11057    visibility: &VisibilityIndex<'_>,
11058    file: &ProjectFile,
11059    source: &str,
11060    function: Node<'_>,
11061    arity: usize,
11062) -> Option<CppScanBinding> {
11063    if function.kind() != "qualified_identifier" {
11064        return None;
11065    }
11066    let qualified = normalize_cpp_reference_text(node_text(function, source));
11067    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
11068    // single component (the shared splitter's operator-token merge keeps
11069    // `operator+`-style names intact), so re-tokenizing with the shared
11070    // structured splitter and peeling the terminal segment reproduces
11071    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
11072    // `cpp_out_of_line_function_owner`'s `qualified` split above.
11073    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11074        brokk_bifrost_core::analyzer::Language::Cpp,
11075        &qualified,
11076    );
11077    let (owner_text, member_name) = match parts.split_last() {
11078        Some((member, owner_parts)) if !owner_parts.is_empty() => {
11079            (owner_parts.join("::"), member.clone())
11080        }
11081        _ => {
11082            let scope = function.child_by_field_name("scope")?;
11083            let name = function.child_by_field_name("name")?;
11084            (
11085                node_text(scope, source).to_string(),
11086                node_text(name, source).to_string(),
11087            )
11088        }
11089    };
11090    let owner = visibility.resolve_type(file, &owner_text)?;
11091    let candidates =
11092        callable_members_accepting_arity(analyzer, visibility, file, &owner, &member_name, arity);
11093    unanimous_return_binding(analyzer, visibility, file, &candidates)
11094}
11095
11096fn resolve_field_method_call_return_binding(
11097    analyzer: &CppGraphSource<'_>,
11098    visibility: &VisibilityIndex<'_>,
11099    file: &ProjectFile,
11100    source: &str,
11101    function: Node<'_>,
11102    arity: usize,
11103    receiver_resolver: Option<&ReceiverResolver<'_>>,
11104) -> Option<CppScanBinding> {
11105    debug_assert_eq!(
11106        function.kind(),
11107        "field_expression",
11108        "the member-call return binding answers only for a field-expression callee"
11109    );
11110    let receiver_resolver = receiver_resolver?;
11111    let field = function.child_by_field_name("field")?;
11112    let member_name = node_text(function_terminal_node(field), source);
11113    let receiver = function
11114        .child_by_field_name("argument")
11115        .or_else(|| function.named_child(0))?;
11116    let owners = receiver_resolver(receiver, source);
11117    let mut candidates = Vec::new();
11118    for owner in owners {
11119        let declaring_owner =
11120            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
11121                EnclosingMemberOwnerResolution::Owner(owner) => owner,
11122                EnclosingMemberOwnerResolution::Missing => continue,
11123                EnclosingMemberOwnerResolution::Ambiguous => return None,
11124            };
11125        candidates.extend(callable_members_accepting_arity(
11126            analyzer,
11127            visibility,
11128            file,
11129            &declaring_owner,
11130            member_name,
11131            arity,
11132        ));
11133    }
11134    unanimous_return_binding(analyzer, visibility, file, &candidates)
11135}
11136
11137fn callable_members_accepting_arity(
11138    analyzer: &CppGraphSource<'_>,
11139    visibility: &VisibilityIndex<'_>,
11140    file: &ProjectFile,
11141    owner: &CodeUnit,
11142    member_name: &str,
11143    arity: usize,
11144) -> Vec<CodeUnit> {
11145    visibility
11146        .visible_members_for_owner_name(file, owner, member_name)
11147        .into_iter()
11148        .filter(|unit| {
11149            unit.is_function()
11150                && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
11151        })
11152        .cloned()
11153        .collect()
11154}
11155
11156fn unanimous_return_binding(
11157    analyzer: &CppGraphSource<'_>,
11158    visibility: &VisibilityIndex<'_>,
11159    file: &ProjectFile,
11160    candidates: &[CodeUnit],
11161) -> Option<CppScanBinding> {
11162    let mut resolved_return: Option<CppScanBinding> = None;
11163    for function in candidates {
11164        let metadata = analyzer.signature_metadata(function);
11165        let return_types = if metadata.is_empty() {
11166            vec![cpp_function_return_type_text(analyzer, function)?]
11167        } else {
11168            metadata
11169                .iter()
11170                .map(|metadata| metadata.return_type_text().map(str::to_string))
11171                .collect::<Option<Vec<_>>>()?
11172        };
11173        for return_text in return_types {
11174            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
11175            let name = normalize_cpp_type_name(&return_text);
11176            let binding = CppScanBinding::from_type_name(
11177                name.clone(),
11178                visibility
11179                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
11180                indirection,
11181            );
11182            if let Some(existing) = resolved_return.as_ref()
11183                && (existing.indirection != binding.indirection
11184                    || match (&existing.unit, &binding.unit) {
11185                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
11186                        (None, None) => existing.type_name != binding.type_name,
11187                        (Some(_), None) | (None, Some(_)) => true,
11188                    })
11189            {
11190                return None;
11191            }
11192            resolved_return = Some(binding);
11193        }
11194    }
11195    resolved_return
11196}
11197
11198fn aliases_from_canonical_source(
11199    cpp: &dyn CppSource,
11200    token: QueryToken<'_>,
11201    file: &ProjectFile,
11202) -> Option<Vec<CppAlias>> {
11203    let source = cpp.declaration_source_facts(token, file)?;
11204    Some(
11205        source
11206            .facts
11207            .declarations
11208            .iter()
11209            .filter_map(|fact| {
11210                let alias = fact.file_scope_alias.as_ref()?;
11211                Some(CppAlias {
11212                    name: alias.name.clone(),
11213                    target: alias.target.clone(),
11214                    namespace: alias.namespace.clone(),
11215                })
11216            })
11217            .collect(),
11218    )
11219}
11220
11221pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
11222    let mut current = node.parent();
11223    while let Some(parent) = current {
11224        match parent.kind() {
11225            "translation_unit"
11226            | "namespace_definition"
11227            | "declaration_list"
11228            | "linkage_specification" => current = parent.parent(),
11229            "template_declaration" => current = parent.parent(),
11230            _ => return false,
11231        }
11232    }
11233    true
11234}
11235
11236pub fn collect_include_closure(
11237    analyzer: &CppGraphSource<'_>,
11238    include_targets: &IncludeTargetIndex,
11239    file: &ProjectFile,
11240    out: &mut HashSet<ProjectFile>,
11241    cancellation: Option<&CancellationToken>,
11242) {
11243    let mut stack = vec![file.clone()];
11244    while let Some(file) = stack.pop() {
11245        if cancellation.is_some_and(CancellationToken::is_cancelled) {
11246            break;
11247        }
11248        if !out.insert(file.clone()) {
11249            continue;
11250        }
11251        for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
11252            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
11253                stack.push(target);
11254            }
11255        }
11256    }
11257}
11258
11259fn parse_macro_parameter_list<T>(
11260    replacement: &str,
11261    read: impl FnOnce(Node<'_>, &str) -> Option<T>,
11262) -> Option<T> {
11263    let parsed = parse_sentinel(
11264        "void __bifrost_macro_parameters(",
11265        SentinelInput::replacement(replacement),
11266        ");",
11267    );
11268    let (source, tree) = (parsed.source, parsed.tree);
11269    let root = tree.root_node();
11270    if root.has_error() {
11271        return None;
11272    }
11273    let declaration = single_named_child_matching(root, |child| !child.is_extra())?;
11274    let declarator = declaration.child_by_field_name("declarator")?;
11275    let parameters = declarator.child_by_field_name("parameters")?;
11276    read(parameters, &source)
11277}
11278
11279fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
11280    parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
11281}
11282
11283/// Interpret opaque macro replacement parameters with the primary producer's
11284/// canonical type spelling, preserving cv/ref qualifiers for overload hiding.
11285pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
11286    parse_macro_parameter_list(
11287        replacement,
11288        crate::declarations::cpp_callable_parameter_types,
11289    )
11290}
11291
11292pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
11293    brokk_bifrost_core::analyzer::model::first_callable_arity(analyzer.signature_metadata(unit))
11294}
11295
11296pub fn cpp_callable_parameter_types(
11297    analyzer: &CppGraphSource<'_>,
11298    unit: &CodeUnit,
11299) -> Option<Vec<String>> {
11300    brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
11301        analyzer.signature_metadata(unit),
11302    )
11303}
11304
11305fn merge_compatible_callable_arities(
11306    left: CallableArity,
11307    right: CallableArity,
11308) -> Option<CallableArity> {
11309    let total = left.total();
11310    let left_repeated = left.accepts(total.saturating_add(1));
11311    let right_repeated = right.accepts(right.total().saturating_add(1));
11312    if total != right.total() || left_repeated != right_repeated {
11313        return None;
11314    }
11315    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
11316    Some(CallableArity::new(required, total, left_repeated))
11317}
11318
11319fn collect_macro_events(
11320    cpp: &dyn CppSource,
11321    token: QueryToken<'_>,
11322    file: &ProjectFile,
11323) -> Vec<MacroEvent> {
11324    let Some(prepared) = cpp.prepared_syntax(token, file) else {
11325        return Vec::new();
11326    };
11327    let source = prepared.source();
11328    let mut events = Vec::new();
11329    let root = prepared.tree().root_node();
11330    let mut stack = vec![root];
11331    while let Some(node) = stack.pop() {
11332        match node.kind() {
11333            "preproc_def" | "preproc_function_def" => {
11334                let Some(name) = node.child_by_field_name("name") else {
11335                    continue;
11336                };
11337                let name = node_text(name, source).to_string();
11338                events.push(MacroEvent::Define {
11339                    name,
11340                    binding: MacroBinding {
11341                        source: file.clone(),
11342                        declaration_byte: node.start_byte(),
11343                        definition: VisibilityIndex::decode_macro_definition(node, source),
11344                        exact: true,
11345                    },
11346                    byte: node.start_byte(),
11347                    conditionals: owning_preprocessor_conditionals(root, node, source),
11348                });
11349                continue;
11350            }
11351            "preproc_include" => {
11352                let Some(path) = node.child_by_field_name("path") else {
11353                    events.push(MacroEvent::Include {
11354                        targets: Vec::new(),
11355                        byte: node.start_byte(),
11356                        conditionals: owning_preprocessor_conditionals(root, node, source),
11357                    });
11358                    continue;
11359                };
11360                let include = crate::imports::include_path_literal_value(path, source);
11361                let targets = include.map_or_else(Vec::new, |include| {
11362                    resolve_include_targets_with_index(file, include, &cpp.include_target_index())
11363                });
11364                // An include no indexed file can answer -- its final path component names
11365                // no analyzable workspace file -- crosses into sources this index does not
11366                // hold, so it cannot hide indexed macro state and must not poison every
11367                // later local macro binding. The include's spelling does not decide that:
11368                // a quoted include falls back to the same search path an angle include
11369                // uses, and esphome's `core/log.h` reaches every component with
11370                // `#include "WString.h"`, an Arduino header absent from the tree (#3057).
11371                // A computed include, and a spelling some indexed file name does match
11372                // without resolving to one target, may still hide indexed macro state and
11373                // therefore fail closed.
11374                if targets.is_empty()
11375                    && include.is_some_and(|include| {
11376                        !cpp.include_target_index().names_indexed_file(include)
11377                    })
11378                {
11379                    continue;
11380                }
11381                events.push(MacroEvent::Include {
11382                    targets,
11383                    byte: node.start_byte(),
11384                    conditionals: owning_preprocessor_conditionals(root, node, source),
11385                });
11386                continue;
11387            }
11388            "preproc_call" => {
11389                let Some(directive) = node.child_by_field_name("directive") else {
11390                    continue;
11391                };
11392                if node_text(directive, source) != "#undef" {
11393                    continue;
11394                }
11395                let name = node
11396                    .child_by_field_name("argument")
11397                    .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
11398                if let Some(name) = name {
11399                    events.push(MacroEvent::Undef {
11400                        name,
11401                        byte: node.start_byte(),
11402                        conditionals: owning_preprocessor_conditionals(root, node, source),
11403                    });
11404                } else {
11405                    events.push(MacroEvent::Invalidate {
11406                        byte: node.start_byte(),
11407                    });
11408                }
11409                continue;
11410            }
11411            _ => {}
11412        }
11413        push_named_children_reversed(node, &mut stack);
11414    }
11415    events.sort_by_key(MacroEvent::byte);
11416    events
11417}
11418
11419fn find_include_activation<'guards>(
11420    cpp: &dyn CppSource,
11421    token: QueryToken<'_>,
11422    file: &ProjectFile,
11423    prepared: &PreparedSyntaxTree,
11424    donor_source: &ProjectFile,
11425    effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11426) -> Option<usize> {
11427    let include_targets = cpp.include_target_index();
11428    let mut direct_includes = Vec::new();
11429    let mut nodes = vec![prepared.tree().root_node()];
11430    // An include activates for the whole file, so only an unconditional
11431    // directive counts here.
11432    while let Some(node) = nodes.pop() {
11433        if node.kind() == "preproc_include" {
11434            let Some(required_guards) =
11435                include_directive_guard_requirements(node, prepared.source(), effective_guards)
11436            else {
11437                continue;
11438            };
11439            if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11440                for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11441                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
11442                        file,
11443                        include,
11444                        &include_targets,
11445                    )) {
11446                        direct_includes.push((node.end_byte(), target));
11447                    }
11448                }
11449            }
11450            continue;
11451        }
11452        push_named_children_reversed(node, &mut nodes);
11453    }
11454    direct_includes.sort_by_key(|(activation, _)| *activation);
11455    let mut known_missing = HashSet::default();
11456    direct_includes
11457        .into_iter()
11458        .find(|(_, direct)| {
11459            unconditional_include_reaches(
11460                cpp,
11461                token,
11462                direct,
11463                donor_source,
11464                file,
11465                &mut known_missing,
11466                effective_guards,
11467            )
11468        })
11469        .map(|(activation, _)| activation)
11470}
11471
11472fn find_conditional_include_projection_index<'guards>(
11473    cpp: &dyn CppSource,
11474    token: QueryToken<'_>,
11475    file: &ProjectFile,
11476    prepared: &PreparedSyntaxTree,
11477    effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11478    on_state: &dyn Fn(),
11479) -> ConditionalIncludeProjectionIndex {
11480    let include_targets = cpp.include_target_index();
11481    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
11482        HashMap::default();
11483    let mut pending = Vec::new();
11484    let mut nodes = vec![prepared.tree().root_node()];
11485    while let Some(node) = nodes.pop() {
11486        if node.kind() == "preproc_include" {
11487            let Some(required_guards) =
11488                include_directive_guard_requirements(node, prepared.source(), effective_guards)
11489            else {
11490                continue;
11491            };
11492            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11493                let Some(target) = unique_include_target(resolve_include_targets_with_index(
11494                    file,
11495                    include,
11496                    &include_targets,
11497                )) else {
11498                    continue;
11499                };
11500                pending.push((target, node.end_byte(), required_guards.clone()));
11501            }
11502            continue;
11503        }
11504        push_named_children_reversed(node, &mut nodes);
11505    }
11506
11507    // Admission checks both the full guard set and the subset contributed by
11508    // incomplete conditional families. A path subsumes another only when both
11509    // sets are subsets: equal full guards can have different proof obligations
11510    // when one route uses a complete #if/#else and another a lone #if.
11511    // Keep the minimal pairs to avoid enumerating dense guard lattices (#2365).
11512    let mut expanded: HashMap<(ProjectFile, usize), Vec<PreprocessorGuardEnvironment>> =
11513        HashMap::default();
11514    while let Some((current_file, activation_byte, path)) = pending.pop() {
11515        let guard_sets = expanded
11516            .entry((current_file.clone(), activation_byte))
11517            .or_default();
11518        if guard_sets.iter().any(|existing| existing.is_subset(&path)) {
11519            continue;
11520        }
11521        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
11522            .drain(..)
11523            .partition(|existing| path.is_subset(existing));
11524        *guard_sets = kept;
11525        guard_sets.push(path.clone());
11526        if !evicted.is_empty()
11527            && let Some(projections) = projections_by_source.get_mut(&current_file)
11528        {
11529            projections.retain(|projection| {
11530                projection.activation_byte != activation_byte
11531                    || !evicted.iter().any(|existing| {
11532                        existing.all == projection.required_guards
11533                            && existing.partial == projection.partial_guards
11534                    })
11535            });
11536        }
11537        on_state();
11538
11539        // A fresh minimal set has no equal in the store: equality would have
11540        // been caught by the subset check above.
11541        projections_by_source
11542            .entry(current_file.clone())
11543            .or_default()
11544            .push(ConditionalIncludeProjection {
11545                activation_byte,
11546                required_guards: path.all.clone(),
11547                partial_guards: path.partial.clone(),
11548            });
11549
11550        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
11551            continue;
11552        };
11553        let mut nodes = vec![current_prepared.tree().root_node()];
11554        while let Some(node) = nodes.pop() {
11555            if node.kind() == "preproc_include" {
11556                let Some(include_guards) = include_directive_guard_requirements(
11557                    node,
11558                    current_prepared.source(),
11559                    effective_guards,
11560                ) else {
11561                    continue;
11562                };
11563                let Some(reached) = path.merged(&include_guards) else {
11564                    continue;
11565                };
11566                for (include, _) in
11567                    include_path_from_node(node, current_prepared.source()).into_iter()
11568                {
11569                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
11570                        &current_file,
11571                        include,
11572                        &include_targets,
11573                    )) else {
11574                        continue;
11575                    };
11576                    pending.push((target, activation_byte, reached.clone()));
11577                }
11578                continue;
11579            }
11580            push_named_children_reversed(node, &mut nodes);
11581        }
11582    }
11583
11584    projections_by_source
11585        .into_iter()
11586        .map(|(source, mut projections)| {
11587            projections.sort_by_key(|projection| projection.activation_byte);
11588            (source, Arc::from(projections))
11589        })
11590        .collect()
11591}
11592
11593/// Decide one conditional include target without materializing every source
11594/// reached by every guard combination. A path `admission` already rejects
11595/// cannot become admissible after adding nested include guards -- both rules
11596/// only ever add requirements -- so discard it before expanding the next
11597/// header.
11598#[allow(clippy::too_many_arguments)]
11599fn find_conditional_include_projection_for_source<'guards>(
11600    cpp: &dyn CppSource,
11601    token: QueryToken<'_>,
11602    file: &ProjectFile,
11603    prepared: &PreparedSyntaxTree,
11604    donor_source: &ProjectFile,
11605    admission: IncludePathAdmission,
11606    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11607    reference_byte: usize,
11608    effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11609    on_state: &dyn Fn(),
11610) -> bool {
11611    let Some(reference_guards) = reference_guards else {
11612        return false;
11613    };
11614    let include_targets = cpp.include_target_index();
11615    let mut pending = Vec::new();
11616    let mut nodes = vec![prepared.tree().root_node()];
11617    while let Some(node) = nodes.pop() {
11618        if node.kind() == "preproc_include" {
11619            let Some(required_guards) =
11620                include_directive_guard_requirements(node, prepared.source(), effective_guards)
11621            else {
11622                continue;
11623            };
11624            if node.end_byte() > reference_byte
11625                || !admission.admits(
11626                    &required_guards.all,
11627                    &required_guards.partial,
11628                    Some(reference_guards),
11629                )
11630            {
11631                continue;
11632            }
11633            for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11634                let Some(target) = unique_include_target(resolve_include_targets_with_index(
11635                    file,
11636                    include,
11637                    &include_targets,
11638                )) else {
11639                    continue;
11640                };
11641                if &target == donor_source {
11642                    return true;
11643                }
11644                pending.push((target, required_guards.clone()));
11645            }
11646            continue;
11647        }
11648        push_named_children_reversed(node, &mut nodes);
11649    }
11650
11651    let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
11652    while let Some((current_file, path)) = pending.pop() {
11653        let guard_sets = expanded.entry(current_file.clone()).or_default();
11654        if guard_sets.contains(&path.all) {
11655            continue;
11656        }
11657        guard_sets.push(path.all.clone());
11658        on_state();
11659
11660        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
11661            continue;
11662        };
11663        let mut nodes = vec![current_prepared.tree().root_node()];
11664        while let Some(node) = nodes.pop() {
11665            if node.kind() == "preproc_include" {
11666                let Some(include_guards) = include_directive_guard_requirements(
11667                    node,
11668                    current_prepared.source(),
11669                    effective_guards,
11670                ) else {
11671                    continue;
11672                };
11673                let Some(reached) = path.merged(&include_guards) else {
11674                    continue;
11675                };
11676                if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11677                    continue;
11678                }
11679                for (include, _) in
11680                    include_path_from_node(node, current_prepared.source()).into_iter()
11681                {
11682                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
11683                        &current_file,
11684                        include,
11685                        &include_targets,
11686                    )) else {
11687                        continue;
11688                    };
11689                    if &target == donor_source {
11690                        return true;
11691                    }
11692                    pending.push((target, reached.clone()));
11693                }
11694                continue;
11695            }
11696            push_named_children_reversed(node, &mut nodes);
11697        }
11698    }
11699    false
11700}
11701
11702/// Whether `translation_unit`'s unconditional `#include` closure reaches
11703/// `header`, directly or through any chain of headers.
11704///
11705/// The include-closure question asked on its own, for
11706/// [`crate::identity::cpp_header_body_files_are_related`]. The walk resolves
11707/// each include the way visibility does -- to a unique target or to nothing --
11708/// so a duplicated basename relates nothing, and it is memoized per file pair
11709/// and language-proof state on the analyzer.
11710///
11711/// The reference position is `translation_unit` itself: the question is
11712/// whether that unit compiles the header, so that unit's own dialect and
11713/// preprocessor context govern the walk.
11714pub fn cpp_include_closure_reaches(
11715    cpp: &dyn CppSource,
11716    token: QueryToken<'_>,
11717    translation_unit: &ProjectFile,
11718    header: &ProjectFile,
11719) -> bool {
11720    let language_guard_stable = OnceCell::new();
11721    let effective = OnceCell::new();
11722    let proven = compile_proven_guard_facts(cpp, translation_unit);
11723    unconditional_include_reaches(
11724        cpp,
11725        token,
11726        translation_unit,
11727        header,
11728        translation_unit,
11729        &mut HashSet::default(),
11730        &|| {
11731            effective
11732                .get_or_init(|| {
11733                    effective_reference_guards(
11734                        cpp,
11735                        translation_unit,
11736                        Some(&HashSet::default()),
11737                        &proven,
11738                        &|| {
11739                            *language_guard_stable.get_or_init(|| {
11740                                include_language_guard_is_stable_with(
11741                                    cpp,
11742                                    token,
11743                                    translation_unit,
11744                                    &mut |file: &ProjectFile| {
11745                                        collect_macro_events(cpp, token, file).into_boxed_slice()
11746                                    },
11747                                    &mut |_: &ProjectFile, prepared: &PreparedSyntaxTree| {
11748                                        collect_structured_include_facts(prepared)
11749                                    },
11750                                )
11751                            })
11752                        },
11753                    )
11754                })
11755                .as_ref()
11756        },
11757    )
11758}
11759
11760fn unconditional_include_reaches<'guards>(
11761    cpp: &dyn CppSource,
11762    token: QueryToken<'_>,
11763    first: &ProjectFile,
11764    donor_source: &ProjectFile,
11765    reference_file: &ProjectFile,
11766    known_missing: &mut HashSet<ProjectFile>,
11767    effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11768) -> bool {
11769    if first == donor_source {
11770        return true;
11771    }
11772    if known_missing.contains(first) {
11773        return false;
11774    }
11775    if let Some(reaches) =
11776        cpp.cached_unconditional_include_reachability(first, donor_source, reference_file)
11777    {
11778        return reaches;
11779    }
11780    let mut visited = HashSet::default();
11781    let mut files = vec![first.clone()];
11782    let include_targets = cpp.include_target_index();
11783    while let Some(file) = files.pop() {
11784        if file == *donor_source {
11785            cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, true);
11786            return true;
11787        }
11788        if known_missing.contains(&file) || !visited.insert(file.clone()) {
11789            continue;
11790        }
11791        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11792            continue;
11793        };
11794        // One cursor for the whole-file walk; see `named_children_iter` (#3097).
11795        let mut cursor = prepared.tree().walk();
11796        let mut nodes = vec![prepared.tree().root_node()];
11797        while let Some(node) = nodes.pop() {
11798            if node.kind() == "preproc_include" {
11799                let Some(required_guards) =
11800                    include_directive_guard_requirements(node, prepared.source(), effective_guards)
11801                else {
11802                    continue;
11803                };
11804                if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11805                    for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
11806                    {
11807                        if let Some(target) = unique_include_target(
11808                            resolve_include_targets_with_index(&file, include, &include_targets),
11809                        ) {
11810                            files.push(target);
11811                        }
11812                    }
11813                }
11814                continue;
11815            }
11816            let first_pushed = nodes.len();
11817            nodes.extend(node.named_children(&mut cursor));
11818            nodes[first_pushed..].reverse();
11819        }
11820    }
11821    known_missing.extend(visited);
11822    cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, false);
11823    false
11824}
11825
11826/// The guards that hold at a reference in `file`: its own source guards,
11827/// the defines the build proves (#2011), and the translation unit's stable
11828/// `__cplusplus` state. A reference whose own environment is unknown stays
11829/// unknown -- the facts extend an environment, they never invent one. Every
11830/// proof and contradiction check against a reference uses this set, so a C
11831/// translation unit never takes an `#ifdef __cplusplus` arm (#7072).
11832fn effective_reference_guards(
11833    cpp: &dyn CppSource,
11834    file: &ProjectFile,
11835    reference_guards: Option<&HashSet<PreprocessorGuard>>,
11836    proven: &HashSet<PreprocessorGuard>,
11837    language_guard_is_stable: &dyn Fn() -> bool,
11838) -> Option<HashSet<PreprocessorGuard>> {
11839    let mut effective = reference_guards.cloned()?;
11840    effective.extend(proven.iter().cloned());
11841    if language_guard_is_stable() {
11842        effective.insert(if reference_uses_c_semantics(cpp, file) {
11843            PreprocessorGuard::Undefined("__cplusplus".to_string())
11844        } else {
11845            PreprocessorGuard::Defined("__cplusplus".to_string())
11846        });
11847    }
11848    Some(effective)
11849}
11850
11851fn include_language_guard_is_stable_with<MacroEvents, IncludeFacts>(
11852    cpp: &dyn CppSource,
11853    token: QueryToken<'_>,
11854    file: &ProjectFile,
11855    mut macro_events: MacroEvents,
11856    mut include_facts: IncludeFacts,
11857) -> bool
11858where
11859    MacroEvents: FnMut(&ProjectFile) -> Box<[MacroEvent]>,
11860    IncludeFacts: FnMut(&ProjectFile, &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]>,
11861{
11862    // A header has no intrinsic dialect. An opaque incoming include can hide
11863    // its compiler context from reverse include attribution. Only a
11864    // translation unit can supply the source-only language fact.
11865    if !crate::imports::is_cpp_translation_unit(file) {
11866        return false;
11867    }
11868    // When compiler data exists, keep its existing authoritative guard proof.
11869    // Source-only dialect facts cannot reconstruct -D/-U ordering.
11870    if !cpp.compile_contexts_for(file).is_empty()
11871        || cpp
11872            .reaching_translation_units(file)
11873            .iter()
11874            .any(|tu| !cpp.compile_contexts_for(tu).is_empty())
11875    {
11876        return false;
11877    }
11878    let mut pending = vec![file.clone()];
11879    let mut visited = HashSet::default();
11880    while let Some(file) = pending.pop() {
11881        if !visited.insert(file.clone()) {
11882            continue;
11883        }
11884        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11885            return false;
11886        };
11887        if macro_events(&file).iter().any(|event| match event {
11888            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
11889                name == "__cplusplus"
11890            }
11891            MacroEvent::Invalidate { .. } => true,
11892            MacroEvent::Include { targets, .. } => targets.is_empty(),
11893        }) {
11894            return false;
11895        }
11896        // Unlike indexed macro-binding replay, intrinsic-macro proof also
11897        // needs every include source: an opaque header may change it.
11898        for fact in include_facts(&file, prepared.as_ref()).iter() {
11899            let targets =
11900                resolve_include_targets_with_index(&file, &fact.path, &cpp.include_target_index());
11901            if targets.is_empty() {
11902                return false;
11903            }
11904            pending.extend(targets);
11905        }
11906    }
11907    true
11908}
11909
11910fn declaration_guard_requirements(
11911    analyzer: &CppGraphSource<'_>,
11912    cpp: &dyn CppSource,
11913    candidate: &CodeUnit,
11914) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11915    if cpp
11916        .prepared_syntax(analyzer.token, candidate.source())
11917        .is_none()
11918    {
11919        return Vec::new();
11920    }
11921    analyzer
11922        .declaration_source_occurrences(candidate)
11923        .into_iter()
11924        .flatten()
11925        .filter_map(|occurrence| {
11926            let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11927            Some((occurrence.range.start_byte, guards))
11928        })
11929        .collect()
11930}
11931
11932/// One declaration range's preprocessor facts, as a visible-at-a-reference
11933/// question reads them.
11934struct DeclarationGuardSite {
11935    /// The byte at which the range introduces the name.
11936    byte: usize,
11937    /// The preprocessor requirements the range's declaration stands under.
11938    guards: HashSet<PreprocessorGuard>,
11939    /// Whether the range is one branch of completed `#if`/`#else` families
11940    /// that all close before the reference.
11941    completed_family_before_reference: bool,
11942}
11943
11944/// The per-range facts behind [`declaration_guard_requirements`], plus the
11945/// reference-relative fact the completed-family rule asks for.
11946fn declaration_guard_sites(
11947    analyzer: &CppGraphSource<'_>,
11948    cpp: &dyn CppSource,
11949    candidate: &CodeUnit,
11950    reference_byte: usize,
11951) -> Vec<DeclarationGuardSite> {
11952    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11953        return Vec::new();
11954    };
11955    let root = prepared.tree().root_node();
11956    analyzer
11957        .declaration_source_occurrences(candidate)
11958        .into_iter()
11959        .flatten()
11960        .filter_map(|occurrence| {
11961            let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11962            Some(DeclarationGuardSite {
11963                byte: occurrence.range.start_byte,
11964                guards,
11965                completed_family_before_reference: {
11966                    root.descendant_for_byte_range(
11967                        occurrence.range.start_byte,
11968                        occurrence.range.end_byte,
11969                    )
11970                    .is_some_and(|node| {
11971                        declaration_follows_completed_family_before_reference(
11972                            node,
11973                            prepared.source(),
11974                            reference_byte,
11975                        )
11976                    })
11977                },
11978            })
11979        })
11980        .collect()
11981}
11982
11983/// Whether every conditional enclosing `node` is a completed
11984/// `#if`/`#elif`/`#else` family that closes before `reference_byte`.
11985///
11986/// A completed family is an exhaustive case analysis: every configuration
11987/// takes exactly one of its branches. A reference sited below the whole family
11988/// is compiled in a configuration that either takes the declaration's branch,
11989/// in which case the declaration is what the name resolves to there, or takes
11990/// another branch, in which case the declaration is simply not part of that
11991/// configuration. Nothing in the reference's own position settles the family,
11992/// so the declaration is one resolution the source presents at the reference
11993/// rather than a contradiction of it (#3297).
11994///
11995/// A lone `#if` is not a case analysis: nothing in the source says any
11996/// configuration defines its macro, so a declaration under one stays
11997/// unprovable and the implication rule keeps it out. Neither does a family
11998/// whose end is not behind the reference: a reference inside the other branch
11999/// contradicts the declaration, and a reference inside the declaration's own
12000/// branch is already covered by implication.
12001fn declaration_follows_completed_family_before_reference(
12002    node: Node<'_>,
12003    source: &str,
12004    reference_byte: usize,
12005) -> bool {
12006    callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
12007}
12008
12009/// Producer-owned completion boundary for all enclosing conditional families.
12010/// Zero is the neutral boundary for a declaration outside those families.
12011pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
12012    let mut cache = PreprocessorContextCache::default();
12013    callable_guard_completion_byte_with_cache(node, source, &mut cache)
12014}
12015
12016pub(crate) fn callable_guard_completion_byte_with_cache(
12017    node: Node<'_>,
12018    source: &str,
12019    cache: &mut PreprocessorContextCache,
12020) -> Option<usize> {
12021    let mut completion_byte = 0;
12022    let mut ancestor = node.parent();
12023    while let Some(conditional) = ancestor {
12024        if matches!(
12025            conditional.kind(),
12026            "preproc_if" | "preproc_ifdef" | "preproc_elif"
12027        ) && !is_file_covering_include_guard(conditional, source)
12028            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12029            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12030        {
12031            let family = preprocessor_conditional_family_root(conditional);
12032            if !preprocessor_conditional_family_has_terminal_else(family, cache) {
12033                return None;
12034            }
12035            completion_byte = completion_byte.max(family.end_byte());
12036        }
12037        ancestor = conditional.parent();
12038    }
12039    Some(completion_byte)
12040}
12041
12042fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
12043    analyzer
12044        .ranges(candidate)
12045        .into_iter()
12046        .map(|range| range.start_byte)
12047        .min()
12048}
12049
12050/// Build facts shared by visibility queries and standalone include identity.
12051fn compile_proven_guard_facts(
12052    cpp: &dyn CppSource,
12053    file: &ProjectFile,
12054) -> HashSet<PreprocessorGuard> {
12055    if let Some(facts) = context_guard_facts(&cpp.compile_contexts_for(file), file) {
12056        return facts;
12057    }
12058    let mut translation_units = cpp.reaching_translation_units(file).into_iter();
12059    let Some(mut facts) = translation_units.next().and_then(|translation_unit| {
12060        context_guard_facts(
12061            &cpp.compile_contexts_for(&translation_unit),
12062            &translation_unit,
12063        )
12064    }) else {
12065        return HashSet::default();
12066    };
12067    for translation_unit in translation_units {
12068        let Some(reached) = context_guard_facts(
12069            &cpp.compile_contexts_for(&translation_unit),
12070            &translation_unit,
12071        ) else {
12072            facts.clear();
12073            break;
12074        };
12075        facts.retain(|fact| reached.contains(fact));
12076        if facts.is_empty() {
12077            break;
12078        }
12079    }
12080    facts
12081}
12082
12083/// Facts every compiler configuration proves. No entry differs from coverage
12084/// with an empty intersection (#2011). The compiler's language selection is
12085/// authoritative, including when it overrides the source extension.
12086fn context_guard_facts(
12087    contexts: &[CppCompileContext],
12088    file: &ProjectFile,
12089) -> Option<HashSet<PreprocessorGuard>> {
12090    let (first, rest) = contexts.split_first()?;
12091    let mut facts = first
12092        .defined_macros
12093        .iter()
12094        .filter(|name| {
12095            rest.iter()
12096                .all(|context| context.defined_macros.contains(*name))
12097        })
12098        .cloned()
12099        .map(PreprocessorGuard::Defined)
12100        .collect::<HashSet<_>>();
12101    let language = first.tu_language(file.rel_path());
12102    // An explicit -D/-U may override the builtin independently of the dialect.
12103    // Surviving defines above still prove their names; an -U alone proves no
12104    // source-level absence, matching the other command-line macro facts.
12105    if contexts
12106        .iter()
12107        .all(|context| !context.cplusplus_macro_configured)
12108        && rest
12109            .iter()
12110            .all(|context| context.tu_language(file.rel_path()) == language)
12111    {
12112        facts.insert(match language {
12113            CompiledLanguage::C => PreprocessorGuard::Undefined("__cplusplus".to_string()),
12114            CompiledLanguage::Cpp => PreprocessorGuard::Defined("__cplusplus".to_string()),
12115        });
12116    }
12117    Some(facts)
12118}
12119
12120pub fn guard_requirements_hold_at_reference(
12121    required: &HashSet<PreprocessorGuard>,
12122    reference: Option<&HashSet<PreprocessorGuard>>,
12123) -> bool {
12124    reference.is_some_and(|active| {
12125        required
12126            .iter()
12127            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
12128    })
12129}
12130
12131fn preprocessor_guard_holds_at_reference(
12132    required: &PreprocessorGuard,
12133    active: &HashSet<PreprocessorGuard>,
12134) -> bool {
12135    if active.contains(required) {
12136        return true;
12137    }
12138    let active_expression =
12139        BooleanGuardExpression::all(active.iter().map(PreprocessorGuard::as_boolean_expression));
12140    active_expression.implies(&required.as_boolean_expression())
12141}
12142
12143/// Candidate discovery rule: two guard sets are compatible when neither
12144/// contradicts the other. Compatibility never establishes proven visibility;
12145/// that requires `guard_requirements_hold_at_reference`.
12146fn guards_compatible_at_reference(
12147    declaration: &HashSet<PreprocessorGuard>,
12148    reference: Option<&HashSet<PreprocessorGuard>>,
12149) -> bool {
12150    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
12151}
12152
12153pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
12154    let mut ancestor = Some(node);
12155    while let Some(current) = ancestor {
12156        if is_cpp_preprocessor_conditional(current)
12157            && preprocessor_conditional_contains_descendant(current, node)
12158        {
12159            return Some(preprocessor_conditional_family_root(current));
12160        }
12161        ancestor = current.parent();
12162    }
12163    None
12164}
12165
12166pub(crate) fn preprocessor_conditional_family_for_declaration_with_cache<'tree>(
12167    node: Node<'tree>,
12168    cache: &mut PreprocessorContextCache,
12169) -> Option<Node<'tree>> {
12170    let mut ancestor = node.parent();
12171    while let Some(current) = ancestor {
12172        if is_cpp_preprocessor_conditional(current)
12173            && preprocessor_conditional_contains_descendant_with_cache(current, node, cache)
12174        {
12175            let family = preprocessor_conditional_family_root(current);
12176            if preprocessor_conditional_family_has_terminal_else(family, cache) {
12177                return Some(family);
12178            }
12179        }
12180        ancestor = current.parent();
12181    }
12182    None
12183}
12184
12185fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
12186    while let Some(parent) = conditional.parent() {
12187        let is_alternative = parent
12188            .child_by_field_name("alternative")
12189            .is_some_and(|alternative| {
12190                alternative.start_byte() == conditional.start_byte()
12191                    && alternative.end_byte() == conditional.end_byte()
12192            });
12193        if !is_alternative {
12194            break;
12195        }
12196        conditional = parent;
12197    }
12198    conditional
12199}
12200
12201fn preprocessor_conditional_family_has_terminal_else(
12202    mut conditional: Node<'_>,
12203    cache: &mut PreprocessorContextCache,
12204) -> bool {
12205    loop {
12206        let Some(alternative) = conditional.child_by_field_name("alternative") else {
12207            return cache
12208                .displaced_branch(conditional)
12209                .is_some_and(|directive| directive.is_else);
12210        };
12211        match alternative.kind() {
12212            "preproc_else" => return true,
12213            "preproc_elif" => conditional = alternative,
12214            _ => return false,
12215        }
12216    }
12217}
12218
12219/// The undecidable preprocessor guards an `#include` directive stands under,
12220/// or `None` when its conditional context keeps the directive out of a
12221/// translation unit of the reference's language: a false constant, a
12222/// condition this analyzer cannot read, or a `__cplusplus` branch that
12223/// language never takes.
12224///
12225/// This is [`callable_declaration_guard_requirements`]'s rule for the include
12226/// edge, and the language test is what keeps
12227/// [`IncludePathAdmission::Compatible`] honest: an empty active guard set
12228/// never implies `defined(__cplusplus)`, but it never contradicts it either,
12229/// so without this a `.c` file would reach the C++ arm of every
12230/// `#ifdef __cplusplus` / `#else` include pair.
12231fn include_directive_guard_requirements<'guards>(
12232    node: Node<'_>,
12233    source: &str,
12234    effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
12235) -> Option<PreprocessorGuardEnvironment> {
12236    let mut required = preprocessor_guard_environment_by_family(node, source)?;
12237    if !required.all.is_empty() {
12238        let effective = effective_guards();
12239        if !guards_compatible_at_reference(&required.all, effective) {
12240            return None;
12241        }
12242        for guards in [&mut required.all, &mut required.partial] {
12243            guards.retain(|guard| {
12244                !effective
12245                    .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active))
12246            });
12247        }
12248    }
12249    Some(required)
12250}
12251
12252pub fn preprocessor_guard_environment(
12253    node: Node<'_>,
12254    source: &str,
12255) -> Option<HashSet<PreprocessorGuard>> {
12256    let mut cache = PreprocessorContextCache::default();
12257    preprocessor_guard_environment_with_cache(node, source, &mut cache)
12258}
12259
12260pub(crate) fn preprocessor_guard_environment_with_cache(
12261    node: Node<'_>,
12262    source: &str,
12263    cache: &mut PreprocessorContextCache,
12264) -> Option<HashSet<PreprocessorGuard>> {
12265    preprocessor_guard_environment_by_family_with_cache(node, source, cache)
12266        .map(|environment| environment.all)
12267}
12268
12269/// The preprocessor guards `node` stands under, split by whether the
12270/// conditional that contributes each one belongs to a family that covers every
12271/// configuration.
12272///
12273/// A complete `#if`/`#else` or `#if`/`#elif`/`#else` family is a case analysis
12274/// every configuration takes exactly one branch of, which is why the resolver
12275/// already reads a declaration through one (`exhaustive_guard_family_activation`,
12276/// `complementary_same_fqn_type_declarations`). A lone `#if` is not: nothing in
12277/// the source says any configuration defines that macro, so only the build can
12278/// decide it, and it stays in `partial`.
12279#[derive(Clone)]
12280struct PreprocessorGuardEnvironment {
12281    all: HashSet<PreprocessorGuard>,
12282    partial: HashSet<PreprocessorGuard>,
12283}
12284
12285impl PreprocessorGuardEnvironment {
12286    fn is_subset(&self, other: &Self) -> bool {
12287        self.all.is_subset(&other.all) && self.partial.is_subset(&other.partial)
12288    }
12289
12290    /// The environment of a path standing under both `self` and `other`, or
12291    /// `None` when the two contradict. A guard a lone `#if` contributes on
12292    /// either side stays undecided for the whole path.
12293    fn merged(&self, other: &Self) -> Option<Self> {
12294        Some(Self {
12295            all: merge_preprocessor_guards(&self.all, &other.all)?,
12296            partial: self.partial.union(&other.partial).cloned().collect(),
12297        })
12298    }
12299}
12300
12301fn preprocessor_guard_environment_by_family(
12302    node: Node<'_>,
12303    source: &str,
12304) -> Option<PreprocessorGuardEnvironment> {
12305    let mut cache = PreprocessorContextCache::default();
12306    preprocessor_guard_environment_by_family_with_cache(node, source, &mut cache)
12307}
12308
12309fn preprocessor_guard_environment_by_family_with_cache(
12310    node: Node<'_>,
12311    source: &str,
12312    cache: &mut PreprocessorContextCache,
12313) -> Option<PreprocessorGuardEnvironment> {
12314    let mut all = HashSet::default();
12315    let mut partial = HashSet::default();
12316    let mut ancestor = node.parent();
12317    while let Some(conditional) = ancestor {
12318        if matches!(
12319            conditional.kind(),
12320            "preproc_if" | "preproc_ifdef" | "preproc_elif"
12321        ) && !is_file_covering_include_guard(conditional, source)
12322            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12323            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12324        {
12325            let guard = preprocessor_guard_for_descendant(conditional, node, source, cache)?;
12326            match guard {
12327                PreprocessorGuard::Constant(true) => {
12328                    ancestor = conditional.parent();
12329                    continue;
12330                }
12331                PreprocessorGuard::Constant(false) => return None,
12332                _ => {}
12333            }
12334            if all.contains(&guard.negated()) {
12335                return None;
12336            }
12337            if !preprocessor_conditional_family_has_terminal_else(
12338                preprocessor_conditional_family_root(conditional),
12339                cache,
12340            ) {
12341                partial.insert(guard.clone());
12342            }
12343            all.insert(guard);
12344        }
12345        ancestor = conditional.parent();
12346    }
12347    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
12348        match guard {
12349            PreprocessorGuard::Constant(true) => {}
12350            PreprocessorGuard::Constant(false) => return None,
12351            _ => {
12352                if all.contains(&guard.negated()) {
12353                    return None;
12354                }
12355                // The family of a conditional the parser split across a
12356                // statement boundary is not readable from the tree, so it
12357                // counts as undecided.
12358                partial.insert(guard.clone());
12359                all.insert(guard);
12360            }
12361        }
12362    }
12363    Some(PreprocessorGuardEnvironment { all, partial })
12364}
12365
12366pub(crate) fn callable_preprocessor_guard_requirements_with_cache(
12367    node: Node<'_>,
12368    source: &str,
12369    cache: &mut PreprocessorContextCache,
12370) -> Option<HashSet<PreprocessorGuard>> {
12371    let mut guards = HashSet::default();
12372    for guard in callable_preprocessor_ancestor_guards_with_cache(node, source, cache)? {
12373        match guard {
12374            PreprocessorGuard::Constant(true) => {}
12375            PreprocessorGuard::Constant(false) => return None,
12376            _ => {
12377                if guards.contains(&guard.negated()) {
12378                    return None;
12379                }
12380                guards.insert(guard);
12381            }
12382        }
12383    }
12384    Some(guards)
12385}
12386
12387fn callable_preprocessor_ancestor_guards(
12388    node: Node<'_>,
12389    source: &str,
12390) -> Option<Vec<PreprocessorGuard>> {
12391    let mut cache = PreprocessorContextCache::default();
12392    callable_preprocessor_ancestor_guards_with_cache(node, source, &mut cache)
12393}
12394
12395fn callable_preprocessor_ancestor_guards_with_cache(
12396    node: Node<'_>,
12397    source: &str,
12398    cache: &mut PreprocessorContextCache,
12399) -> Option<Vec<PreprocessorGuard>> {
12400    let mut guards = Vec::new();
12401    let mut ancestor = node.parent();
12402    while let Some(conditional) = ancestor {
12403        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
12404            && !is_file_covering_include_guard(conditional, source)
12405            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12406            && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12407        {
12408            guards.push(preprocessor_guard_for_descendant(
12409                conditional,
12410                node,
12411                source,
12412                cache,
12413            )?);
12414        }
12415        ancestor = conditional.parent();
12416    }
12417    Some(guards)
12418}
12419
12420fn fragmented_statement_preprocessor_guard(
12421    descendant: Node<'_>,
12422    source: &str,
12423) -> Option<PreprocessorGuard> {
12424    // A conditional that starts before `} else if (...) {` crosses the
12425    // enclosing statement's grammar boundary. tree-sitter leaves its opener
12426    // as a `preproc_if` with a missing terminator in the consequence and
12427    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
12428    // those structured nodes before restoring the guard to intervening uses.
12429    let mut ancestor = descendant.parent();
12430    while let Some(statement) = ancestor {
12431        if statement.kind() == "if_statement"
12432            && let (Some(consequence), Some(alternative)) = (
12433                statement.child_by_field_name("consequence"),
12434                statement.child_by_field_name("alternative"),
12435            )
12436            && alternative.start_byte() <= descendant.start_byte()
12437            && descendant.end_byte() <= alternative.end_byte()
12438        {
12439            let mut cursor = consequence.walk();
12440            let openers = consequence
12441                .named_children(&mut cursor)
12442                .filter(|child| {
12443                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
12444                        && child
12445                            .child(child.child_count().saturating_sub(1))
12446                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
12447                })
12448                .collect::<Vec<_>>();
12449            if openers.len() != 1 {
12450                ancestor = statement.parent();
12451                continue;
12452            }
12453
12454            let mut terminators = Vec::new();
12455            let mut stack = vec![alternative];
12456            while let Some(node) = stack.pop() {
12457                if node.kind() == "preproc_call"
12458                    && node.start_byte() >= descendant.end_byte()
12459                    && node
12460                        .child_by_field_name("directive")
12461                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
12462                {
12463                    terminators.push(node);
12464                    continue;
12465                }
12466                push_named_children_reversed(node, &mut stack);
12467            }
12468            if terminators.len() == 1 {
12469                return simple_preprocessor_guard(openers[0], source);
12470            }
12471        }
12472        ancestor = statement.parent();
12473    }
12474    None
12475}
12476
12477pub(crate) fn preprocessor_guard_for_descendant(
12478    conditional: Node<'_>,
12479    descendant: Node<'_>,
12480    source: &str,
12481    cache: &mut PreprocessorContextCache,
12482) -> Option<PreprocessorGuard> {
12483    let guard = simple_preprocessor_guard(conditional, source)?;
12484    match preprocessor_branch_of_descendant(conditional, descendant, cache) {
12485        PreprocessorBranch::Consequence => Some(guard),
12486        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
12487        // descendant in any later branch must first exclude the parent branch,
12488        // then collect the nested `preproc_elif` guard from its own ancestor.
12489        PreprocessorBranch::Structured(alternative)
12490            if matches!(alternative.kind(), "preproc_else" | "preproc_elif") =>
12491        {
12492            Some(guard.negated())
12493        }
12494        PreprocessorBranch::DisplacedElse => Some(guard.negated()),
12495        PreprocessorBranch::Structured(_) | PreprocessorBranch::Unreadable => None,
12496    }
12497}
12498
12499/// The branch of `conditional` that holds `descendant`.
12500enum PreprocessorBranch<'tree> {
12501    Consequence,
12502    /// Inside the structured `alternative` field.
12503    Structured(Node<'tree>),
12504    /// After an `#else` the parser displaced into an `ERROR` of the
12505    /// consequence ([`cpp_displaced_preprocessor_branch`]).
12506    DisplacedElse,
12507    /// After a displaced `#elif`, whose condition has no structured node.
12508    Unreadable,
12509}
12510
12511fn preprocessor_branch_of_descendant<'tree>(
12512    conditional: Node<'tree>,
12513    descendant: Node<'_>,
12514    cache: &mut PreprocessorContextCache,
12515) -> PreprocessorBranch<'tree> {
12516    if let Some(alternative) =
12517        conditional
12518            .child_by_field_name("alternative")
12519            .filter(|alternative| {
12520                alternative.start_byte() <= descendant.start_byte()
12521                    && descendant.end_byte() <= alternative.end_byte()
12522            })
12523    {
12524        return PreprocessorBranch::Structured(alternative);
12525    }
12526    match cache.displaced_branch(conditional) {
12527        Some(directive) if directive.end_byte <= descendant.start_byte() => {
12528            if directive.is_else {
12529                PreprocessorBranch::DisplacedElse
12530            } else {
12531                PreprocessorBranch::Unreadable
12532            }
12533        }
12534        _ => PreprocessorBranch::Consequence,
12535    }
12536}
12537
12538pub(crate) fn preprocessor_conditional_contains_descendant(
12539    conditional: Node<'_>,
12540    descendant: Node<'_>,
12541) -> bool {
12542    cpp_displaced_preprocessor_boundary(conditional)
12543        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
12544}
12545
12546fn preprocessor_conditional_contains_descendant_with_cache(
12547    conditional: Node<'_>,
12548    descendant: Node<'_>,
12549    cache: &mut PreprocessorContextCache,
12550) -> bool {
12551    cache
12552        .displaced_boundary_end(conditional)
12553        .is_none_or(|end_byte| descendant.end_byte() <= end_byte)
12554}
12555
12556pub fn merge_preprocessor_guards(
12557    left: &HashSet<PreprocessorGuard>,
12558    right: &HashSet<PreprocessorGuard>,
12559) -> Option<HashSet<PreprocessorGuard>> {
12560    let mut merged = left.clone();
12561    for guard in right {
12562        let boolean_negation = guard.as_boolean_expression().negated();
12563        if merged.contains(&guard.negated())
12564            || merged
12565                .iter()
12566                .map(PreprocessorGuard::as_boolean_expression)
12567                .any(|existing| existing == boolean_negation)
12568        {
12569            return None;
12570        }
12571        merged.insert(guard.clone());
12572    }
12573    Some(merged)
12574}
12575
12576fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
12577    if conditional.kind() == "preproc_ifdef" {
12578        let name = conditional.child_by_field_name("name")?;
12579        let name = node_text(name, source).to_string();
12580        return match conditional.child(0)?.kind() {
12581            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
12582            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
12583            _ => None,
12584        };
12585    }
12586    let condition = conditional.child_by_field_name("condition")?;
12587    simple_preprocessor_expression_guard(condition, source).or_else(|| {
12588        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
12589            node_text(condition, source),
12590        )))
12591    })
12592}
12593
12594fn simple_preprocessor_expression_guard(
12595    expression: Node<'_>,
12596    source: &str,
12597) -> Option<PreprocessorGuard> {
12598    match expression.kind() {
12599        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
12600            node_text(expression, source).to_string(),
12601        ))),
12602        "number_literal" => match node_text(expression, source).trim() {
12603            "0" => Some(PreprocessorGuard::Constant(false)),
12604            "1" => Some(PreprocessorGuard::Constant(true)),
12605            _ => None,
12606        },
12607        "preproc_defined" => {
12608            let identifier = (0..expression.named_child_count())
12609                .filter_map(|index| expression.named_child(index))
12610                .find(|child| child.kind() == "identifier")?;
12611            Some(PreprocessorGuard::Defined(
12612                node_text(identifier, source).to_string(),
12613            ))
12614        }
12615        "unary_expression"
12616            if expression
12617                .child_by_field_name("operator")
12618                .is_some_and(|operator| operator.kind() == "!") =>
12619        {
12620            simple_preprocessor_expression_guard(
12621                expression.child_by_field_name("argument")?,
12622                source,
12623            )
12624            .map(|guard| guard.negated())
12625        }
12626        "parenthesized_expression" => (0..expression.named_child_count())
12627            .filter_map(|index| expression.named_child(index))
12628            .next()
12629            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
12630        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
12631            expression, source,
12632        ))),
12633        _ => None,
12634    }
12635}
12636
12637fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
12638    match expression.kind() {
12639        "number_literal" => match node_text(expression, source).trim() {
12640            "0" => BooleanGuardExpression::Constant(false),
12641            "1" => BooleanGuardExpression::Constant(true),
12642            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12643                expression, source,
12644            ))),
12645        },
12646        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
12647        "preproc_defined" => {
12648            let identifier = (0..expression.named_child_count())
12649                .filter_map(|index| expression.named_child(index))
12650                .find(|child| child.kind() == "identifier");
12651            identifier.map_or_else(
12652                || {
12653                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12654                        expression, source,
12655                    )))
12656                },
12657                |identifier| {
12658                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
12659                },
12660            )
12661        }
12662        "unary_expression"
12663            if expression
12664                .child_by_field_name("operator")
12665                .is_some_and(|operator| operator.kind() == "!") =>
12666        {
12667            expression.child_by_field_name("argument").map_or_else(
12668                || {
12669                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12670                        expression, source,
12671                    )))
12672                },
12673                |argument| boolean_preprocessor_expression(argument, source).negated(),
12674            )
12675        }
12676        "parenthesized_expression" => (0..expression.named_child_count())
12677            .filter_map(|index| expression.named_child(index))
12678            .next()
12679            .map_or_else(
12680                || {
12681                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12682                        expression, source,
12683                    )))
12684                },
12685                |child| boolean_preprocessor_expression(child, source),
12686            ),
12687        "binary_expression" => {
12688            let operands = || {
12689                Some((
12690                    boolean_preprocessor_expression(
12691                        expression.child_by_field_name("left")?,
12692                        source,
12693                    ),
12694                    boolean_preprocessor_expression(
12695                        expression.child_by_field_name("right")?,
12696                        source,
12697                    ),
12698                ))
12699            };
12700            match expression
12701                .child_by_field_name("operator")
12702                .map(|operator| operator.kind())
12703            {
12704                Some("&&") => operands().map_or_else(
12705                    || {
12706                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12707                            expression, source,
12708                        )))
12709                    },
12710                    |(left, right)| BooleanGuardExpression::all([left, right]),
12711                ),
12712                Some("||") => operands().map_or_else(
12713                    || {
12714                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12715                            expression, source,
12716                        )))
12717                    },
12718                    |(left, right)| BooleanGuardExpression::any([left, right]),
12719                ),
12720                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12721                    expression, source,
12722                ))),
12723            }
12724        }
12725        _ => {
12726            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
12727        }
12728    }
12729}
12730
12731fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
12732    if targets.len() == 1 {
12733        targets.pop()
12734    } else {
12735        None
12736    }
12737}
12738
12739fn callable_declaration_nodes<'tree>(
12740    analyzer: &CppGraphSource<'_>,
12741    prepared: &'tree PreparedSyntaxTree,
12742    candidate: &CodeUnit,
12743) -> Vec<Node<'tree>> {
12744    let root = prepared.tree().root_node();
12745    analyzer
12746        .ranges(candidate)
12747        .into_iter()
12748        .filter_map(|range| {
12749            let mut declaration =
12750                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
12751            // A declaration an attribute-like macro invocation swallowed lives
12752            // inside the `ERROR` the parser left, not inside a `declaration`
12753            // node, so that envelope is where the climb stops (#2552).
12754            //
12755            // The climb can also run out of ancestors. A collapse that takes a
12756            // whole file with it -- whisper.cpp's `include/whisper.h` from its
12757            // first `WHISPER_DEPRECATED(` to the end (#3094) -- leaves no
12758            // envelope around most of what a region reparse recovers there, and
12759            // the recovered declaration's own `;` can fall outside the envelope
12760            // it does leave, so the range spans the container rather than one
12761            // node. Either way the declaration stands at the scope the climb
12762            // ended in, which is what the callers ask these nodes about.
12763            while !matches!(
12764                declaration.kind(),
12765                "declaration" | "field_declaration" | "function_definition"
12766            ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
12767                declaration,
12768                prepared.source(),
12769            ) {
12770                let Some(parent) = declaration.parent() else {
12771                    break;
12772                };
12773                declaration = parent;
12774            }
12775            Some(declaration)
12776        })
12777        .collect()
12778}
12779
12780fn real_function_definition_ancestor<'tree>(
12781    node: Node<'tree>,
12782    source: &str,
12783) -> Option<Node<'tree>> {
12784    let mut ancestor = node.parent();
12785    while let Some(node) = ancestor {
12786        if node.kind() == "function_definition"
12787            && !is_recovered_declaration_scope_container(node, source)
12788        {
12789            return Some(node);
12790        }
12791        ancestor = node.parent();
12792    }
12793    None
12794}
12795
12796fn callable_declaration_activation_in_file(
12797    analyzer: &CppGraphSource<'_>,
12798    candidate: &CodeUnit,
12799    reference: &CallableReferenceContext<'_>,
12800) -> Option<usize> {
12801    analyzer
12802        .declaration_source_occurrences(candidate)?
12803        .into_iter()
12804        .filter_map(|occurrence| {
12805            let activation = occurrence.fact.callable_activation?;
12806            let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
12807            callable_guard_requirements_are_visible(
12808                &guards,
12809                occurrence.fact.callable_guard_completion_byte,
12810                reference,
12811            )
12812            .then_some(activation)
12813        })
12814        .min()
12815}
12816
12817fn callable_guard_requirements_are_visible(
12818    required: &HashSet<PreprocessorGuard>,
12819    completion_byte: Option<usize>,
12820    reference: &CallableReferenceContext<'_>,
12821) -> bool {
12822    if required.is_empty() || guard_requirements_hold_at_reference(required, reference.guards()) {
12823        return true;
12824    }
12825    reference.position.as_ref().is_some_and(|position| {
12826        completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
12827            && guards_compatible_at_reference(required, reference.guards())
12828    })
12829}
12830
12831type ReferenceGuardAugmenter<'a> =
12832    dyn Fn(Option<&HashSet<PreprocessorGuard>>) -> Option<HashSet<PreprocessorGuard>> + 'a;
12833
12834/// The reference side of a callable visibility question.
12835///
12836/// Whole-file arity activation has no exact reference position and therefore
12837/// no source guard environment, but retains file-level language and build facts.
12838/// Position-based questions augment their source environment with the same
12839/// effective context as declaration and include proofs.
12840// These cells never escape the resolution call that constructs them. Its
12841// prepared-source read and the first guard augmentation dominate every local
12842// hit in that same capture. Do not retain this context in VisibilityIndexData.
12843struct CallableReferenceContext<'a> {
12844    effective_guards: &'a ReferenceGuardAugmenter<'a>,
12845    file: &'a ProjectFile,
12846    position: Option<CallableReferencePosition<'a>>,
12847    file_guards: OnceCell<Option<HashSet<PreprocessorGuard>>>,
12848}
12849
12850/// One reference position plus its preprocessor guard environment. The
12851/// environment is computed on demand because most declarations carry no
12852/// non-trivial guard.
12853struct CallableReferencePosition<'a> {
12854    prepared: &'a PreparedSyntaxTree,
12855    byte: usize,
12856    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
12857}
12858
12859impl CallableReferenceContext<'_> {
12860    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
12861        let Some(position) = self.position.as_ref() else {
12862            return self
12863                .file_guards
12864                .get_or_init(|| (self.effective_guards)(Some(&HashSet::default())))
12865                .as_ref();
12866        };
12867        position
12868            .guards
12869            .get_or_init(|| {
12870                let raw = position
12871                    .prepared
12872                    .tree()
12873                    .root_node()
12874                    .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
12875                    .and_then(|node| {
12876                        preprocessor_guard_environment(node, position.prepared.source())
12877                    });
12878                (self.effective_guards)(raw.as_ref())
12879            })
12880            .as_ref()
12881    }
12882}
12883
12884/// The undecidable preprocessor guards `node` stands under, or `None` when its
12885/// conditional context excludes it from `reference`'s translation unit outright:
12886/// a false constant, a condition this analyzer cannot read, or a `__cplusplus`
12887/// branch the reference's language never takes.
12888///
12889/// Collecting one guard per enclosing conditional makes the whole walk a
12890/// conjunction of the declaration's requirements. Which comparison those
12891/// requirements then face depends on where the declaration lives: a same-file
12892/// declaration shares the reference's configuration, so the reference's active
12893/// guards must imply them, while a foreign file resolves its own conditionals
12894/// and only has to stay compatible with the reference.
12895fn callable_declaration_guard_requirements(
12896    node: Node<'_>,
12897    source: &str,
12898    reference: &CallableReferenceContext<'_>,
12899) -> Option<HashSet<PreprocessorGuard>> {
12900    let mut required = HashSet::default();
12901    for guard in callable_preprocessor_ancestor_guards(node, source)? {
12902        match guard {
12903            PreprocessorGuard::Constant(true) => {}
12904            PreprocessorGuard::Constant(false) => return None,
12905            guard => {
12906                required.insert(guard);
12907            }
12908        }
12909    }
12910    guards_compatible_at_reference(&required, reference.guards()).then_some(required)
12911}
12912
12913/// Whether a declaration in the reference's own file is co-active with the
12914/// reference: one translation unit resolves every conditional the same way, so
12915/// the reference's active guards must imply the declaration's requirements.
12916///
12917/// The exception is a declaration in one branch of a completed `#if`/`#else`
12918/// family the reference follows: the reference's own position settles nothing
12919/// about that family, so a compatible branch is one resolution the source
12920/// presents at the reference (#3297), exactly as
12921/// [`VisibilityIndex::external_type_candidate_visible_in_context`] reads a type
12922/// declaration there.
12923fn callable_preprocessor_context_is_visible_for_reference(
12924    node: Node<'_>,
12925    source: &str,
12926    reference: &CallableReferenceContext<'_>,
12927) -> bool {
12928    let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12929        return false;
12930    };
12931    if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12932        return true;
12933    }
12934    reference.position.as_ref().is_some_and(|position| {
12935        guards_compatible_at_reference(&required, reference.guards())
12936            && declaration_follows_completed_family_before_reference(node, source, position.byte)
12937    })
12938}
12939
12940fn flattened_macro_namespace_declaration_matches(
12941    analyzer: &CppGraphSource<'_>,
12942    _cpp: &dyn CppSource,
12943    reference_file: &ProjectFile,
12944    visible_declaration: &CodeUnit,
12945    qualified_candidate: &CodeUnit,
12946    reference_byte: usize,
12947) -> bool {
12948    // Namespace-opening macros can leave tree-sitter unable to retain the
12949    // namespace owner after a later recovery point. In that shape the forward
12950    // declaration is indexed at translation-unit scope, while the definition
12951    // still has its qualified owner. Require all surviving structural evidence
12952    // before treating the declaration as activation for that definition.
12953    if visible_declaration.kind() != qualified_candidate.kind()
12954        || visible_declaration.identifier() != qualified_candidate.identifier()
12955        || visible_declaration.signature() != qualified_candidate.signature()
12956        || !visible_declaration.package_name().is_empty()
12957        || qualified_candidate.package_name().is_empty()
12958    {
12959        return false;
12960    }
12961
12962    let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
12963        return false;
12964    };
12965    occurrences.into_iter().any(|occurrence| {
12966        occurrence
12967            .displaced_namespace_closing_brace
12968            .is_some_and(|closing_brace| {
12969                visible_declaration.source() != reference_file || closing_brace < reference_byte
12970            })
12971    })
12972}
12973
12974pub(crate) fn flattened_macro_namespace_components(
12975    declaration: Node<'_>,
12976    source: &str,
12977) -> Option<Vec<String>> {
12978    flattened_macro_function_namespace_components(declaration, source)
12979        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12980}
12981
12982fn flattened_macro_function_namespace_components(
12983    declaration: Node<'_>,
12984    source: &str,
12985) -> Option<Vec<String>> {
12986    let body = declaration
12987        .parent()
12988        .filter(|parent| parent.kind() == "compound_statement")?;
12989    let function = body.parent()?;
12990    if function.child_by_field_name("body") != Some(body) {
12991        return None;
12992    }
12993    let namespace_name = recovered_macro_namespace_name(function, source)?;
12994    let mut components = enclosing_namespace_components(declaration, source)?;
12995    components.push(namespace_name);
12996    Some(components)
12997}
12998
12999/// The namespace name a namespace-opening macro token displaced into a
13000/// synthetic `function_definition`, or `None` when `function` is not that
13001/// recovery shape.
13002///
13003/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
13004/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
13005/// the macro token, whose declarator is the namespace name behind an `ERROR`
13006/// holding the `namespace` keyword, and whose body spans the whole namespace
13007/// region. The macro-declaration grammar keeps the same envelope clean: the
13008/// token is a `macro_modifier` (itself admitted only where the parse would
13009/// otherwise contain an error) and the keyword occupies the type field. The
13010/// matching `*_NAMESPACE_END` sibling is what separates the recovery artifact
13011/// from a real function definition.
13012fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
13013    if function.kind() != "function_definition" {
13014        return None;
13015    }
13016    let body = function
13017        .child_by_field_name("body")
13018        .filter(|body| body.kind() == "compound_statement")?;
13019    let mut cursor = function.walk();
13020    let prefix = function
13021        .named_children(&mut cursor)
13022        .take_while(|child| child.start_byte() < body.start_byte())
13023        .filter(|child| child.kind() != "comment")
13024        .collect::<Vec<_>>();
13025    let begin_index = prefix.iter().rposition(|child| {
13026        flattened_macro_sentinel_name(*child, source)
13027            .is_some_and(|name| is_namespace_begin_sentinel(&name))
13028    })?;
13029    let mut identifiers = Vec::new();
13030    let mut stack = prefix[begin_index + 1..]
13031        .iter()
13032        .rev()
13033        .copied()
13034        .collect::<Vec<_>>();
13035    while let Some(current) = stack.pop() {
13036        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
13037            identifiers.push(identifier);
13038            continue;
13039        }
13040        let mut cursor = current.walk();
13041        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
13042        stack.extend(children.into_iter().rev());
13043    }
13044    let [keyword, namespace_name] = identifiers.as_slice() else {
13045        return None;
13046    };
13047    // The namespace keyword and the complete sentinel envelope establish the
13048    // identifier's role. Capitalization does not establish a macro binding.
13049    if keyword != "namespace" || namespace_name.is_empty() {
13050        return None;
13051    }
13052    let mut next = function.next_named_sibling();
13053    let next = loop {
13054        let candidate = next?;
13055        next = candidate.next_named_sibling();
13056        if candidate.kind() != "comment" {
13057            break candidate;
13058        }
13059    };
13060    flattened_macro_sentinel_name(next, source)
13061        .is_some_and(|name| is_namespace_end_sentinel(&name))
13062        .then(|| namespace_name.clone())
13063}
13064
13065/// A `function_definition` that exists only because tree-sitter recovered a
13066/// macro-decorated class head or a namespace-opening macro token. A declaration
13067/// in such a body keeps class or namespace scope, so a scope walk must step over
13068/// the wrapper instead of treating the declaration as block local.
13069pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
13070    crate::declarations::is_recovered_exported_class_container(node, source)
13071        || crate::declarations::is_recovered_fragmented_partial_specialization_container(
13072            node, source,
13073        )
13074        || recovered_macro_namespace_name(node, source).is_some()
13075}
13076
13077fn flattened_macro_error_namespace_components(
13078    declaration: Node<'_>,
13079    source: &str,
13080) -> Option<Vec<String>> {
13081    let parent = declaration
13082        .parent()
13083        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
13084    let mut cursor = parent.walk();
13085    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
13086    let declaration_index = siblings
13087        .iter()
13088        .position(|candidate| same_node(*candidate, declaration))?;
13089    let begin_index = (0..declaration_index).rev().find(|index| {
13090        flattened_macro_sentinel_name(siblings[*index], source)
13091            .is_some_and(|name| is_namespace_begin_sentinel(&name))
13092    })?;
13093
13094    let significant = siblings[begin_index + 1..declaration_index]
13095        .iter()
13096        .copied()
13097        .filter(|node| node.kind() != "comment")
13098        .collect::<Vec<_>>();
13099    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
13100        return None;
13101    };
13102    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
13103        return None;
13104    }
13105    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
13106    if significant[2..].iter().any(|node| {
13107        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
13108            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
13109        })
13110    }) {
13111        return None;
13112    }
13113
13114    let mut saw_namespace_close = false;
13115    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
13116        if sibling.kind() == "comment" {
13117            continue;
13118        }
13119        if !saw_namespace_close {
13120            if direct_unmatched_closing_brace(sibling) {
13121                saw_namespace_close = true;
13122                continue;
13123            }
13124            if flattened_macro_sentinel_name(sibling, source).is_some() {
13125                return None;
13126            }
13127            continue;
13128        }
13129        if !flattened_macro_sentinel_name(sibling, source)
13130            .is_some_and(|name| is_namespace_end_sentinel(&name))
13131        {
13132            return None;
13133        }
13134        let mut components = enclosing_namespace_components(declaration, source)?;
13135        components.push(namespace_name);
13136        return Some(components);
13137    }
13138    None
13139}
13140
13141fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
13142    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
13143    // an `expression_statement` with a missing semicolon; inside a namespace
13144    // body the same token stays a bare `type_identifier`. Under the
13145    // macro-declaration grammar the token is wrapped in a `macro_modifier`
13146    // wherever it decorates a declaration head, and a stacked `END`/`BEGIN`
13147    // pair heads the next region's bogus function with the trailing `END` as
13148    // its first modifier child.
13149    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
13150        node.named_child(0)?
13151    } else {
13152        node
13153    };
13154    let candidates = [
13155        direct_cpp_identifier_name(node, source),
13156        node.child_by_field_name("type")
13157            .and_then(|type_node| direct_cpp_identifier_name(type_node, source)),
13158        macro_modifier_identifier(node)
13159            .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source))),
13160        {
13161            let mut cursor = node.walk();
13162            node.named_children(&mut cursor)
13163                .find(|child| child.kind() != "comment")
13164                .and_then(macro_modifier_identifier)
13165                .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source)))
13166        },
13167    ];
13168    candidates.into_iter().flatten().find(|candidate| {
13169        cpp_export_macro_token(candidate)
13170            && (is_namespace_begin_sentinel(candidate) || is_namespace_end_sentinel(candidate))
13171    })
13172}
13173
13174/// Namespace-opening macros are spelled both ways in the wild:
13175/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
13176#[derive(Clone, Copy, PartialEq, Eq)]
13177enum NamespaceSentinel {
13178    Begin,
13179    End,
13180}
13181
13182fn namespace_sentinel(name: &str) -> Option<NamespaceSentinel> {
13183    if name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE") {
13184        Some(NamespaceSentinel::Begin)
13185    } else if name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE") {
13186        Some(NamespaceSentinel::End)
13187    } else {
13188        None
13189    }
13190}
13191
13192fn is_namespace_begin_sentinel(name: &str) -> bool {
13193    namespace_sentinel(name) == Some(NamespaceSentinel::Begin)
13194}
13195
13196fn is_namespace_end_sentinel(name: &str) -> bool {
13197    namespace_sentinel(name) == Some(NamespaceSentinel::End)
13198}
13199
13200fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
13201    if node.kind() != "ERROR" || node.named_child_count() != 1 {
13202        return None;
13203    }
13204    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
13205    (!cpp_export_macro_token(&name)).then_some(name)
13206}
13207
13208fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
13209    if !matches!(
13210        node.kind(),
13211        "identifier" | "namespace_identifier" | "type_identifier"
13212    ) {
13213        return None;
13214    }
13215    let name = normalize_cpp_whitespace(node_text(node, source));
13216    (!name.is_empty()).then_some(name)
13217}
13218
13219fn guard_requirement_sets_match(
13220    left: &[(usize, HashSet<PreprocessorGuard>)],
13221    right: &[(usize, HashSet<PreprocessorGuard>)],
13222) -> bool {
13223    left.len() == right.len()
13224        && left.iter().all(|(_, left_guards)| {
13225            right
13226                .iter()
13227                .any(|(_, right_guards)| left_guards == right_guards)
13228        })
13229        && right.iter().all(|(_, right_guards)| {
13230            left.iter()
13231                .any(|(_, left_guards)| right_guards == left_guards)
13232        })
13233}
13234
13235pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
13236    node.kind() == "ERROR"
13237        && (0..node.child_count())
13238            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
13239}
13240
13241/// The preprocessor requirements a scope-level binding (a using declaration or
13242/// directive, or a namespace alias) stands under, or `None` when its guard
13243/// environment cannot be represented.
13244pub fn binding_site_guards(node: Node<'_>, source: &str) -> Option<HashSet<PreprocessorGuard>> {
13245    if callable_preprocessor_context_is_visible(node, source) {
13246        Some(HashSet::default())
13247    } else {
13248        preprocessor_guard_environment(node, source)
13249    }
13250}
13251
13252pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
13253    let mut ancestor = node.parent();
13254    while let Some(parent) = ancestor {
13255        if is_cpp_preprocessor_conditional(parent)
13256            && !is_file_covering_include_guard(parent, source)
13257            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
13258        {
13259            return false;
13260        }
13261        ancestor = parent.parent();
13262    }
13263    true
13264}
13265
13266fn is_split_cpp_language_linkage_wrapper(
13267    conditional: Node<'_>,
13268    descendant: Node<'_>,
13269    source: &str,
13270) -> bool {
13271    if conditional.child_by_field_name("alternative").is_some()
13272        || !matches!(
13273            simple_preprocessor_guard(conditional, source),
13274            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
13275        )
13276    {
13277        return false;
13278    }
13279    let mut current = descendant.parent();
13280    let linkage = loop {
13281        let Some(node) = current else {
13282            return false;
13283        };
13284        if node == conditional {
13285            return false;
13286        }
13287        if node.kind() == "linkage_specification" {
13288            break node;
13289        }
13290        current = node.parent();
13291    };
13292    if linkage
13293        .child_by_field_name("value")
13294        .is_none_or(|value| node_text(value, source) != "\"C\"")
13295    {
13296        return false;
13297    }
13298    let Some(body) = linkage.child_by_field_name("body") else {
13299        return false;
13300    };
13301    // Walk the body with cursors: an indexed `named_child(i)` restarts from
13302    // the first child, so indexed loops over a header-sized linkage body made
13303    // each call quadratic, and this runs once per declaration inside it
13304    // (#3397).
13305    let mut cursor = body.walk();
13306    let closes_opening_branch = body
13307        .named_children(&mut cursor)
13308        .take_while(|child| child.end_byte() <= descendant.start_byte())
13309        .any(|child| {
13310            child.kind() == "preproc_call"
13311                && child
13312                    .child_by_field_name("directive")
13313                    .is_some_and(|directive| node_text(directive, source) == "#endif")
13314        });
13315    let mut cursor = body.walk();
13316    let reopens_for_closing_brace = body
13317        .named_children(&mut cursor)
13318        .skip_while(|child| child.start_byte() < descendant.end_byte())
13319        .any(|child| {
13320            matches!(
13321                simple_preprocessor_guard(child, source),
13322                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
13323            ) && {
13324                let mut tokens = child.walk();
13325                child
13326                    .children(&mut tokens)
13327                    .any(|token| token.kind() == "#endif" && token.is_missing())
13328            }
13329        });
13330    closes_opening_branch && reopens_for_closing_brace
13331}
13332
13333/// The argument list a call-shaped node supplies: `f(args)`, `new T(args)`,
13334/// `T{args}` and the member initializer `: field(args)`, whose grammar gives its
13335/// argument list no field name.
13336pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
13337    node.child_by_field_name("arguments")
13338        .or_else(|| node.child_by_field_name("parameters"))
13339        .or_else(|| node.child_by_field_name("value"))
13340        .or_else(|| first_named_child_of_kind(node, "argument_list"))
13341        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
13342}
13343
13344pub fn call_arity(node: Node<'_>) -> usize {
13345    call_arguments_node(node)
13346        .map(|args| argument_children(args).count())
13347        .unwrap_or(0)
13348}
13349
13350pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
13351    let recovered_block_arguments = recovered_block_literal_arguments(node);
13352    (0..node.child_count())
13353        .filter_map(move |index| node.child(index))
13354        .filter(|child| child.is_named() && !child.is_extra())
13355        // The `^(` opening a Clang block literal argument survives the
13356        // macro-declaration grammar's bogus function envelope as an ERROR in
13357        // the parameter list. It only opens the block: the parameter
13358        // declaration that follows it already stands for the block argument.
13359        .filter(move |child| node.kind() != "parameter_list" || !block_literal_open_error(*child))
13360        .flat_map(move |child| {
13361            if let Some((raw, left, right)) = recovered_block_arguments
13362                && child == raw
13363            {
13364                [Some(left), Some(right)]
13365            } else {
13366                [Some(child), None]
13367            }
13368        })
13369        .flatten()
13370}
13371
13372/// The `^(` token pair that opens a Clang block literal argument, retained by
13373/// the bogus function envelope's parameter list as an `ERROR`. The block's
13374/// parameter declaration follows it and counts as the one logical argument.
13375fn block_literal_open_error(node: Node<'_>) -> bool {
13376    if node.kind() != "ERROR" || node.is_missing() {
13377        return false;
13378    }
13379    let mut cursor = node.walk();
13380    let tokens = node.children(&mut cursor).collect::<Vec<_>>();
13381    matches!(tokens.as_slice(), [caret, open] if caret.kind() == "^" && open.kind() == "(")
13382}
13383
13384/// Recover the two ordinary C values that the C++ grammar folds into one
13385/// `new_expression` for `callee(new, trailing)`.
13386///
13387/// The malformed node has an exact grammar-owned shape: the anonymous `new`
13388/// token, an extra `ERROR` containing only the comma token, and the trailing
13389/// value in the `type` field.  The caller supplies the compilation-dialect
13390/// proof from [`reference_uses_c_semantics`]; a C++ source therefore never
13391/// reinterprets a real new-expression through this path.
13392pub fn recovered_c_new_expression_arguments(
13393    node: Node<'_>,
13394    uses_c_semantics: bool,
13395) -> Option<[Node<'_>; 2]> {
13396    if !uses_c_semantics || node.kind() != "new_expression" {
13397        return None;
13398    }
13399    let parent = node.parent()?;
13400    if parent.kind() != "argument_list" {
13401        return None;
13402    }
13403    let keyword = node.child(0)?;
13404    let error = node.child(1)?;
13405    let trailing = node.child(2)?;
13406    if node.child(3).is_some()
13407        || keyword.kind() != "new"
13408        || keyword.is_named()
13409        || keyword.child_count() != 0
13410        || error.kind() != "ERROR"
13411        || !error.is_extra()
13412        || error.child_count() != 1
13413        || error.child(0).is_none_or(|comma| comma.kind() != ",")
13414        || node.child_by_field_name("type") != Some(trailing)
13415        || trailing.kind() != "type_identifier"
13416    {
13417        return None;
13418    }
13419    Some([keyword, trailing])
13420}
13421
13422/// The recovered C value covering one focused source range, starting from any
13423/// node within the malformed new-expression.
13424pub fn recovered_c_new_expression_argument_at(
13425    mut node: Node<'_>,
13426    start_byte: usize,
13427    end_byte: usize,
13428    uses_c_semantics: bool,
13429) -> Option<Node<'_>> {
13430    // The recovery only exists for C sources, and the climb below reaches the
13431    // root before it can answer no. `Node::parent` re-descends from the root on
13432    // every step, so leaving the check inside the loop made every scanned node
13433    // of a C++ file pay a full quadratic ancestor walk for an answer settled by
13434    // the file's language (#3097).
13435    if !uses_c_semantics {
13436        return None;
13437    }
13438    loop {
13439        if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
13440            return arguments.into_iter().find(|argument| {
13441                argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
13442            });
13443        }
13444        node = node.parent()?;
13445    }
13446}
13447
13448fn recovered_c_keyword_argument_count(
13449    file: &ProjectFile,
13450    call: Node<'_>,
13451    arguments: Node<'_>,
13452    source: &str,
13453) -> usize {
13454    // A C identifier that is a C++ keyword can be displaced twice by the C++
13455    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
13456    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
13457    // the enclosing C function before restoring the otherwise dropped slot;
13458    // ABI15's collapsed function-head shape is handled by the fallback below.
13459    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
13460        return 0;
13461    }
13462    let mut ancestor = Some(call);
13463    let function = loop {
13464        let Some(current) = ancestor else {
13465            return recovered_c_keyword_argument_count_from_collapsed_function(call, arguments)
13466                .unwrap_or(0);
13467        };
13468        if current.kind() == "function_definition" {
13469            break current;
13470        }
13471        ancestor = current.parent();
13472    };
13473    let Some(parameters) = function
13474        .child_by_field_name("declarator")
13475        .and_then(|declarator| declarator.child_by_field_name("parameters"))
13476    else {
13477        return 0;
13478    };
13479    let displaced_parameter_keywords = (0..parameters.child_count())
13480        .filter_map(|index| parameters.child(index))
13481        .filter(|error| error.kind() == "ERROR")
13482        .filter_map(|error| {
13483            let parameter = error.prev_named_sibling()?;
13484            if parameter.kind() != "parameter_declaration"
13485                || parameter.end_byte() != error.start_byte()
13486                || extract_variable_name(parameter, source).is_some()
13487            {
13488                return None;
13489            }
13490            let mut children = (0..error.child_count())
13491                .filter_map(|index| error.child(index))
13492                .filter(|child| !child.is_extra() && !child.is_missing());
13493            let keyword = children.next()?;
13494            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
13495                .then_some(keyword)
13496        })
13497        .collect::<Vec<_>>();
13498    if displaced_parameter_keywords.is_empty() {
13499        return 0;
13500    }
13501
13502    (0..arguments.child_count())
13503        .filter_map(|index| arguments.child(index))
13504        .filter(|error| error.kind() == "ERROR" && error.is_extra())
13505        .filter(|error| {
13506            let mut children = (0..error.child_count())
13507                .filter_map(|index| error.child(index))
13508                .filter(|child| !child.is_extra() && !child.is_missing());
13509            let Some(comma) = children.next() else {
13510                return false;
13511            };
13512            let Some(keyword) = children.next() else {
13513                return false;
13514            };
13515            children.next().is_none()
13516                && comma.kind() == ","
13517                && !keyword.is_named()
13518                && keyword.child_count() == 0
13519                && displaced_parameter_keywords
13520                    .iter()
13521                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
13522        })
13523        .count()
13524}
13525
13526/// ABI15 can collapse a C function head into nested `ERROR` nodes when its
13527/// parameter name is a C++ keyword. The call still has a structured body, and
13528/// the outer error retains the function type, name, and opening parenthesis;
13529/// recover the displaced parameter only from those CST relationships. Text
13530/// matching would also accept an unbound call after the collapsed body.
13531fn recovered_c_keyword_argument_count_from_collapsed_function(
13532    call: Node<'_>,
13533    arguments: Node<'_>,
13534) -> Option<usize> {
13535    let argument_keywords = children_iter(arguments)
13536        .filter(|error| error.kind() == "ERROR" && error.is_extra())
13537        .filter_map(|error| {
13538            let mut children =
13539                children_iter(error).filter(|child| !child.is_extra() && !child.is_missing());
13540            let comma = children.next()?;
13541            let keyword = children.next()?;
13542            (children.next().is_none()
13543                && comma.kind() == ","
13544                && !keyword.is_named()
13545                && keyword.child_count() == 0)
13546                .then_some(keyword)
13547        })
13548        .collect::<Vec<_>>();
13549    if argument_keywords.is_empty() {
13550        return Some(0);
13551    }
13552
13553    // The collapsed function keeps its body under the parameter ERROR, while
13554    // ordinary control-flow blocks add compound_statement layers below it.
13555    // Stop before crossing a real callable body, whose parameters are a
13556    // separate binding scope.
13557    let mut ancestor = call.parent();
13558    let body = loop {
13559        let current = ancestor?;
13560        if matches!(current.kind(), "function_definition" | "lambda_expression") {
13561            return None;
13562        }
13563        if current.kind() == "compound_statement"
13564            && current
13565                .parent()
13566                .is_some_and(|parent| parent.kind() == "ERROR")
13567        {
13568            break current;
13569        }
13570        ancestor = current.parent();
13571    };
13572    let parameter_error = body.parent().filter(|parent| parent.kind() == "ERROR")?;
13573    let function_error = parameter_error
13574        .parent()
13575        .filter(|parent| parent.kind() == "ERROR")?;
13576    let function_children = children_iter(function_error).collect::<Vec<_>>();
13577    let parameter_error_index = function_children
13578        .iter()
13579        .position(|child| same_node(*child, parameter_error))?;
13580    // ERROR nodes expose their retained field labels by child index, while
13581    // child_by_field_name does not return their type child in runtime 0.25.
13582    let has_return_type = function_children[..parameter_error_index.saturating_sub(2)]
13583        .iter()
13584        .enumerate()
13585        .any(|(index, child)| {
13586            function_error.field_name_for_child(index as u32) == Some("type")
13587                && !child.is_extra()
13588                && !child.is_missing()
13589                && matches!(
13590                    child.kind(),
13591                    "primitive_type" | "sized_type_specifier" | "type_identifier"
13592                )
13593        });
13594    if parameter_error_index < 2
13595        || !has_return_type
13596        || function_children[parameter_error_index - 1].kind() != "("
13597        || function_children[parameter_error_index - 2].kind() != "identifier"
13598    {
13599        return Some(0);
13600    }
13601
13602    let parameter_children = children_iter(parameter_error).collect::<Vec<_>>();
13603    let body_index = parameter_children
13604        .iter()
13605        .position(|child| same_node(*child, body))?;
13606    let close_parenthesis_index = parameter_children[..body_index]
13607        .iter()
13608        .position(|child| child.kind() == ")")?;
13609    let parameter_keywords = parameter_children[..close_parenthesis_index]
13610        .iter()
13611        .enumerate()
13612        .filter(|(index, child)| {
13613            !child.is_named()
13614                && !child.is_extra()
13615                && !child.is_missing()
13616                && child.child_count() == 0
13617                && argument_keywords
13618                    .iter()
13619                    .any(|keyword| keyword.kind_id() == child.kind_id())
13620                && collapsed_c_parameter_name_position(&parameter_children, *index)
13621        })
13622        .map(|(_, child)| *child)
13623        .collect::<Vec<_>>();
13624    Some(
13625        argument_keywords
13626            .iter()
13627            .filter(|keyword| {
13628                parameter_keywords
13629                    .iter()
13630                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
13631            })
13632            .count(),
13633    )
13634}
13635
13636/// The ABI15 collapsed head retains a parameter's type and pointer declarator
13637/// as direct children. Require that exact segment before accepting an
13638/// anonymous keyword as its name; a keyword after an `=` or attribute must not
13639/// borrow the same recovery.
13640fn collapsed_c_parameter_name_position(children: &[Node<'_>], keyword_index: usize) -> bool {
13641    let segment_start = children[..keyword_index]
13642        .iter()
13643        .rposition(|child| matches!(child.kind(), "(" | ","))
13644        .map_or(0, |index| index + 1);
13645    let mut saw_type = false;
13646    let mut saw_pointer = false;
13647    for child in &children[segment_start..keyword_index] {
13648        if child.kind() == "*" && !child.is_extra() && !child.is_missing() {
13649            saw_pointer = true;
13650            continue;
13651        }
13652        if saw_pointer {
13653            return false;
13654        }
13655        if !child.is_named() || child.is_extra() || child.is_missing() {
13656            return false;
13657        }
13658        if child.kind() == "type_qualifier" {
13659            continue;
13660        }
13661        if matches!(
13662            child.kind(),
13663            "class_specifier"
13664                | "dependent_type"
13665                | "enum_specifier"
13666                | "identifier"
13667                | "primitive_type"
13668                | "qualified_identifier"
13669                | "scoped_identifier"
13670                | "struct_specifier"
13671                | "template_type"
13672                | "type_identifier"
13673                | "union_specifier"
13674        ) {
13675            saw_type = true;
13676            continue;
13677        }
13678        return false;
13679    }
13680    saw_type && saw_pointer
13681}
13682
13683fn recovered_block_literal_arguments<'tree>(
13684    arguments: Node<'tree>,
13685) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
13686    if arguments.kind() != "argument_list" {
13687        return None;
13688    }
13689    let mut raw_arguments = (0..arguments.child_count())
13690        .filter_map(|index| arguments.child(index))
13691        .filter(|child| child.is_named() && !child.is_extra());
13692    let raw = raw_arguments.next()?;
13693    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
13694        return None;
13695    }
13696
13697    let left = raw.child_by_field_name("left")?;
13698    if left.is_missing() || left.start_byte() == left.end_byte() {
13699        return None;
13700    }
13701    let right = raw.child_by_field_name("right")?;
13702    if right.kind() != "compound_literal_expression"
13703        || right.is_missing()
13704        || right
13705            .child_by_field_name("type")
13706            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
13707        || right
13708            .child_by_field_name("value")
13709            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
13710    {
13711        return None;
13712    }
13713    let has_intervening_error = (0..raw.child_count())
13714        .filter_map(|index| raw.child(index))
13715        .any(|child| {
13716            child.kind() == "ERROR"
13717                && !child.is_missing()
13718                && child.start_byte() >= left.end_byte()
13719                && child.end_byte() <= right.start_byte()
13720        });
13721    has_intervening_error.then_some((raw, left, right))
13722}
13723
13724pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
13725    match node.kind() {
13726        "new_expression" => node
13727            .child_by_field_name("type")
13728            .or_else(|| node.named_child(0)),
13729        "compound_literal_expression" => node.child_by_field_name("type"),
13730        "call_expression" => node.child_by_field_name("function"),
13731        _ => None,
13732    }
13733}
13734
13735/// The structured type named by a C-style cast expression.
13736///
13737/// Tree-sitter wraps the actual type syntax in a `type_descriptor`.  Return its
13738/// structured `type` field so resolution retains qualified and nested syntax
13739/// without reparsing source text.
13740pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
13741    if node.kind() != "cast_expression" {
13742        return None;
13743    }
13744    let descriptor = node.child_by_field_name("type")?;
13745    if descriptor.kind() == "type_descriptor" {
13746        descriptor.child_by_field_name("type")
13747    } else {
13748        Some(descriptor)
13749    }
13750}
13751
13752/// The expression a field access reads its member from.
13753///
13754/// That is normally the node in the `argument` field. A preprocessor
13755/// conditional inside a call's argument list defeats the grammar: tree-sitter
13756/// ends the argument list at the `#if` line, then reads the text that follows
13757/// `#endif` as a field access on the whole call expression and leaves the real
13758/// receiver in an `ERROR` node between the `argument` child and the operator.
13759/// mbedtls writes exactly that at `library/ssl_tls.c:4999`, where
13760/// `ssl->session->encrypt_then_mac` is one conditionally compiled call argument
13761/// (#2982).
13762///
13763/// `.` and `->` read whatever ends immediately before them in either tree, so
13764/// answer from the operator's position instead of trusting the `argument`
13765/// field, and unwrap the single expression a displaced `ERROR` holds.
13766pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
13767    debug_assert_eq!(field.kind(), "field_expression");
13768    let operator = field.child_by_field_name("operator")?;
13769    let mut cursor = field.walk();
13770    let receiver = field
13771        .named_children(&mut cursor)
13772        .filter(|child| child.end_byte() <= operator.start_byte())
13773        .last()?;
13774    if receiver.kind() != "ERROR" {
13775        return Some(receiver);
13776    }
13777    (receiver.named_child_count() == 1)
13778        .then(|| receiver.named_child(0))
13779        .flatten()
13780}
13781
13782pub fn field_initializer_constructs_target(
13783    node: Node<'_>,
13784    ctx: &ScanCtx<'_>,
13785    owner: &CodeUnit,
13786) -> bool {
13787    // A qualified name in a constructor initializer denotes a base
13788    // subobject constructor (`namespace::Base(args)`), not a member field.  The
13789    // field-initializer grammar exposes the qualified name as one structured
13790    // `qualified_identifier`; resolve its owner through the same lexical type
13791    // machinery used for ordinary C++ type references before considering the
13792    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
13793    // qualified non-constructor member, and an unresolved owner out of the
13794    // target constructor's inverse usage set.
13795    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
13796        return qualified_base_initializer_constructs_target(node, ctx, owner);
13797    }
13798    let Some(name) = node
13799        .child_by_field_name("name")
13800        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
13801        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
13802    else {
13803        return false;
13804    };
13805    let field_name = node_text(name, ctx.source);
13806    ctx.visibility
13807        .visible_identifier_candidates(ctx.file, field_name)
13808        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
13809        .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
13810}
13811
13812/// Whether a member initializer for `unit` runs a constructor of the type the
13813/// member declares.
13814///
13815/// Only a member that holds the type by value does. `Owner* p` initialized as
13816/// `p(nullptr)` binds a pointer and `Owner& r` as `r(other)` binds a
13817/// reference; neither runs an `Owner` constructor, even though both declare
13818/// `Owner` as the receiver type a member access through them resolves (#3286).
13819/// A member whose declaration the index cannot decode keeps the by-value
13820/// reading, which is what the declared-type match already assumed.
13821fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
13822    field_initializer_holds_subobject(&ctx.analyzer, ctx.visibility, unit)
13823}
13824
13825fn qualified_base_initializer_constructs_target(
13826    node: Node<'_>,
13827    ctx: &ScanCtx<'_>,
13828    owner: &CodeUnit,
13829) -> bool {
13830    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
13831        return false;
13832    };
13833    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
13834        return false;
13835    };
13836    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
13837        return false;
13838    };
13839    let resolves_target = |components: &[String]| {
13840        matches!(
13841            ctx.visibility.resolve_type_components_lexically_for_target(
13842                &ctx.analyzer,
13843                ctx.file,
13844                components,
13845                is_globally_qualified_cpp_name(qualified),
13846                &lexical_scope,
13847                owner,
13848            ),
13849            LexicalTypeResolution::Resolved { unit, .. }
13850                if same_visible_symbol(&unit, owner)
13851        )
13852    };
13853    if resolves_target(&components) {
13854        return true;
13855    }
13856
13857    // Some real-world code spells a base mem-initializer as
13858    // `Base::Base(args)`. In that structured path the final component repeats
13859    // the constructor name; resolve the preceding type path. The terminal
13860    // identity check prevents an arbitrary qualified member from taking this
13861    // route.
13862    components
13863        .last()
13864        .is_some_and(|terminal| terminal == owner.identifier())
13865        && resolves_target(&components[..components.len() - 1])
13866}
13867
13868fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13869    ctx.visibility
13870        .field_declared_type_fact(&ctx.analyzer, unit)
13871        .is_some_and(|fact| {
13872            ctx.visibility
13873                .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
13874                || ctx.visibility.resolves_to_type(
13875                    &ctx.analyzer,
13876                    ctx.file,
13877                    &normalize_field_type_text(&fact.type_text),
13878                    owner,
13879                )
13880        })
13881}
13882
13883pub fn field_declared_binding(
13884    analyzer: &CppGraphSource<'_>,
13885    visibility: &VisibilityIndex<'_>,
13886    visible_from: &ProjectFile,
13887    field: &CodeUnit,
13888) -> Option<CppScanBinding> {
13889    let (normalized, resolved, indirection) =
13890        field_declared_type_binding(analyzer, visibility, visible_from, field)?;
13891    let resolved = resolved
13892        .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
13893    Some(CppScanBinding::from_type_name(
13894        normalized,
13895        resolved,
13896        indirection,
13897    ))
13898}
13899
13900/// Whether a member-initializer `field(args)` runs the declared type's
13901/// constructor: only a member that holds the type by value does. `Owner* p`
13902/// initialized as `p(nullptr)` binds a pointer and `Owner& r` as `r(other)`
13903/// binds a reference; neither runs an `Owner` constructor (#3286). A member
13904/// whose declaration the index cannot decode keeps the by-value reading, the
13905/// same assumption the usage scan's declared-type match makes.
13906pub fn field_initializer_holds_subobject(
13907    analyzer: &CppGraphSource<'_>,
13908    visibility: &VisibilityIndex<'_>,
13909    field: &CodeUnit,
13910) -> bool {
13911    visibility
13912        .field_declared_type_fact(analyzer, field)
13913        .is_none_or(|fact| !fact.binds_indirectly)
13914}
13915
13916/// Resolve the receiver type minted for a named declarator on an anonymous C
13917/// aggregate, such as `struct { int r; } c`. The declaration index preserves
13918/// the aggregate as the nested class `Owner$c`, but the field's type fact has
13919/// no spelling that can name that class. Confirm the anonymous aggregate from
13920/// its parsed declaration, then use the owner's structured child relationship
13921/// to recover the one corresponding receiver type.
13922fn anonymous_aggregate_field_owner(
13923    analyzer: &CppGraphSource<'_>,
13924    visibility: &VisibilityIndex<'_>,
13925    visible_from: &ProjectFile,
13926    field: &CodeUnit,
13927) -> Option<CodeUnit> {
13928    let owner = type_owner_of(analyzer, field)?;
13929    if !owner.is_class() {
13930        return None;
13931    }
13932    let declaration = analyzer.get_source(field, false)?;
13933    let mut parser = Parser::new();
13934    parser
13935        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13936        .ok()?;
13937    let tree = parser.parse(&declaration, None)?;
13938    let mut stack = vec![tree.root_node()];
13939    while let Some(node) = stack.pop() {
13940        if matches!(node.kind(), "declaration" | "field_declaration")
13941            && let Some(type_node) = node
13942                .child_by_field_name("type")
13943                .or_else(|| first_type_child(node))
13944            && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
13945            && type_node.child_by_field_name("name").is_none()
13946            && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
13947        {
13948            let matches = visibility
13949                .visible_members_for_owner_name(visible_from, &owner, field.identifier())
13950                .into_iter()
13951                .filter(|child| child.is_class() && child.identifier() == field.identifier())
13952                .collect::<Vec<_>>();
13953            return match matches.as_slice() {
13954                [child] => Some((*child).clone()),
13955                _ => None,
13956            };
13957        }
13958        let mut cursor = node.walk();
13959        stack.extend(node.named_children(&mut cursor));
13960    }
13961    None
13962}
13963
13964/// Resolve an anonymous C aggregate's generated owner from its declaration
13965/// range. Anonymous local structs and unions have no type name to enter into
13966/// the visibility index; declaration extraction gives them a structured class
13967/// identity keyed by the aggregate node's exact CST range instead. Matching
13968/// that range keeps nested aggregates and unrelated generated owners out of
13969/// the result without inspecting source text.
13970pub fn anonymous_aggregate_owner(
13971    analyzer: &CppGraphSource<'_>,
13972    file: &ProjectFile,
13973    node: Node<'_>,
13974) -> Option<CodeUnit> {
13975    if !matches!(node.kind(), "struct_specifier" | "union_specifier")
13976        || node.child_by_field_name("name").is_some()
13977    {
13978        return None;
13979    }
13980    let mut candidates = analyzer
13981        .declarations(file)
13982        .into_iter()
13983        .filter(|candidate| {
13984            candidate.is_class()
13985                && analyzer.ranges(candidate).into_iter().any(|range| {
13986                    range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
13987                })
13988        })
13989        .collect::<Vec<_>>();
13990    candidates.sort_by_key(|candidate| candidate.fq_name());
13991    candidates.dedup();
13992    match candidates.as_slice() {
13993        [candidate] => Some(candidate.clone()),
13994        _ => None,
13995    }
13996}
13997
13998/// Resolve an unqualified-head type reference through C++ block scope.
13999///
14000/// Unqualified lookup searches the enclosing blocks and local class bodies of
14001/// a function before any namespace or member scope of the function itself, so
14002/// a local class hides every same-spelled outer type from the point where it
14003/// is declared. `None` means no enclosing block or local class declares the
14004/// head and ordinary lookup proceeds. A head that a block does declare is
14005/// never answered by an outer type: it resolves to the local class extraction
14006/// minted for that block, or stays `Missing` when no such class exists (a
14007/// local class declared but never defined, or one written where extraction
14008/// mints no identity). C block scope is a separate capability and keeps
14009/// ordinary lookup.
14010pub fn function_local_type_resolution(
14011    analyzer: &CppGraphSource<'_>,
14012    file: &ProjectFile,
14013    node: Node<'_>,
14014    components: &[String],
14015    source: &str,
14016) -> Option<LexicalTypeResolution> {
14017    let (head, rest) = components.split_first()?;
14018    if is_c_source_file(file) {
14019        return None;
14020    }
14021    let declaration = function_local_class_declaration(node, head, source)?;
14022    let Some(mut unit) = function_local_class_unit(analyzer, file, declaration, head, source)
14023    else {
14024        return Some(LexicalTypeResolution::Missing);
14025    };
14026    for component in rest {
14027        let mut nested = analyzer
14028            .declarations(file)
14029            .into_iter()
14030            .filter(|candidate| {
14031                candidate.is_class()
14032                    && candidate.identifier() == component
14033                    && brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
14034                        .is_some_and(|parent| parent == unit.fq_name())
14035            })
14036            .collect::<Vec<_>>();
14037        nested.dedup();
14038        match nested.as_slice() {
14039            [child] => unit = child.clone(),
14040            [] => return Some(LexicalTypeResolution::Missing),
14041            _ => return Some(LexicalTypeResolution::Ambiguous),
14042        }
14043    }
14044    Some(LexicalTypeResolution::Resolved {
14045        components: canonical_cpp_scope_components(&unit),
14046        candidates: vec![unit.clone()],
14047        unit,
14048    })
14049}
14050
14051/// Whether `unit` is a function-local class or a member of one. Extraction
14052/// scopes those through their callable, so a callable segment sits inside the
14053/// identity rather than at its end; no namespace-scope spelling names them.
14054pub fn is_function_local_unit(unit: &CodeUnit) -> bool {
14055    let interner = segment_interner();
14056    unit.fq().segments().split_last().is_some_and(|(_, scope)| {
14057        scope
14058            .iter()
14059            .any(|&segment| interner.resolve(segment).1 == SegmentKind::Member)
14060    })
14061}
14062
14063/// The class-like specifier that block-scope lookup of `name` reaches from
14064/// `node`, innermost scope first, stopping at the outermost function body.
14065///
14066/// A block declares a class at its specifier, so only a declaration that ends
14067/// before the reference is visible there. A local class body sees its own
14068/// injected name and its nested classes: all of them from a member function
14069/// body (the complete-class context), only earlier ones elsewhere in the body.
14070fn function_local_class_declaration<'tree>(
14071    node: Node<'tree>,
14072    name: &str,
14073    source: &str,
14074) -> Option<Node<'tree>> {
14075    // A recovery envelope that tree-sitter parsed as a function body (a
14076    // namespace sentinel, an export-macro class) is declaration scope, not a
14077    // block: the walk ends there, as it does at the translation unit.
14078    let mut path = Vec::new();
14079    let mut child = node;
14080    while let Some(ancestor) = child.parent() {
14081        if ancestor.kind() == "compound_statement"
14082            && ancestor.parent().is_some_and(|function| {
14083                function.kind() == "function_definition"
14084                    && (is_recovered_declaration_scope_container(function, source)
14085                        || crate::graph::extractor::is_malformed_wrapper_function_definition(
14086                            function,
14087                        ))
14088            })
14089        {
14090            break;
14091        }
14092        path.push((ancestor, child));
14093        child = ancestor;
14094    }
14095    let outermost_block = path
14096        .iter()
14097        .rposition(|(ancestor, _)| ancestor.kind() == "compound_statement")?;
14098    let mut in_member_body = false;
14099    for &(ancestor, _) in &path[..=outermost_block] {
14100        match ancestor.kind() {
14101            "compound_statement" => {
14102                if let Some(found) =
14103                    scope_local_class_declaration(ancestor, name, source, Some(node.start_byte()))
14104                {
14105                    return Some(found);
14106                }
14107            }
14108            "function_definition" | "lambda_expression" => in_member_body = true,
14109            "field_declaration_list" => {
14110                let class = ancestor.parent()?;
14111                if local_class_name_is(class, name, source) {
14112                    return Some(class);
14113                }
14114                let before = (!in_member_body).then_some(node.start_byte());
14115                if let Some(found) = scope_local_class_declaration(ancestor, name, source, before) {
14116                    return Some(found);
14117                }
14118                in_member_body = false;
14119            }
14120            _ => {}
14121        }
14122    }
14123    None
14124}
14125
14126/// The last class named `name` that one block or class body declares, among
14127/// those ending before `before` when it is given. A specifier counts as a
14128/// declaration when it has a body or stands alone (`struct W;`); one inside a
14129/// declaration with declarators and no body (`struct W *p;`) is a reference.
14130fn scope_local_class_declaration<'tree>(
14131    scope: Node<'tree>,
14132    name: &str,
14133    source: &str,
14134    before: Option<usize>,
14135) -> Option<Node<'tree>> {
14136    named_children_iter(scope)
14137        .filter_map(scope_level_class_specifier)
14138        .filter(|specifier| before.is_none_or(|before| specifier.end_byte() <= before))
14139        .filter(|specifier| local_class_name_is(*specifier, name, source))
14140        .last()
14141}
14142
14143fn scope_level_class_specifier(statement: Node<'_>) -> Option<Node<'_>> {
14144    let is_class_like = |node: &Node<'_>| {
14145        matches!(
14146            node.kind(),
14147            "class_specifier" | "struct_specifier" | "union_specifier"
14148        )
14149    };
14150    if is_class_like(&statement) {
14151        return Some(statement);
14152    }
14153    if !matches!(
14154        statement.kind(),
14155        "declaration" | "field_declaration" | "type_definition"
14156    ) {
14157        return None;
14158    }
14159    let specifier = statement
14160        .child_by_field_name("type")
14161        .filter(is_class_like)?;
14162    (specifier.child_by_field_name("body").is_some()
14163        || statement.child_by_field_name("declarator").is_none())
14164    .then_some(specifier)
14165}
14166
14167fn local_class_name_is(specifier: Node<'_>, name: &str, source: &str) -> bool {
14168    specifier
14169        .child_by_field_name("name")
14170        .is_some_and(|name_node| {
14171            name_node.kind() == "type_identifier" && node_text(name_node, source) == name
14172        })
14173}
14174
14175/// The unit extraction minted for the local class `declaration` names. A
14176/// forward declaration names the class its own scope defines.
14177fn function_local_class_unit(
14178    analyzer: &CppGraphSource<'_>,
14179    file: &ProjectFile,
14180    declaration: Node<'_>,
14181    name: &str,
14182    source: &str,
14183) -> Option<CodeUnit> {
14184    let definition = if declaration.child_by_field_name("body").is_some() {
14185        declaration
14186    } else {
14187        let mut scope = declaration.parent()?;
14188        if scope.kind() != "compound_statement" && scope.kind() != "field_declaration_list" {
14189            scope = scope.parent()?;
14190        }
14191        named_children_iter(scope)
14192            .filter_map(scope_level_class_specifier)
14193            .filter(|specifier| specifier.child_by_field_name("body").is_some())
14194            .find(|specifier| local_class_name_is(*specifier, name, source))?
14195    };
14196    let mut candidates = analyzer
14197        .declarations(file)
14198        .into_iter()
14199        .filter(|candidate| {
14200            candidate.is_class()
14201                && candidate.identifier() == name
14202                && is_function_local_unit(candidate)
14203                && analyzer.ranges(candidate).into_iter().any(|range| {
14204                    range.start_byte == definition.start_byte()
14205                        && range.end_byte == definition.end_byte()
14206                })
14207        })
14208        .collect::<Vec<_>>();
14209    candidates.dedup();
14210    match candidates.as_slice() {
14211        [candidate] => Some(candidate.clone()),
14212        _ => None,
14213    }
14214}
14215
14216/// The one logical type the candidates name, or why they do not name one.
14217fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
14218    let Some(first) = candidates.first() else {
14219        return Err(TypeCandidateFailure::Unresolvable);
14220    };
14221    if candidates
14222        .iter()
14223        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
14224    {
14225        Ok((*first).clone())
14226    } else {
14227        Err(TypeCandidateFailure::Ambiguous)
14228    }
14229}
14230
14231fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
14232    logical_type_candidate(candidates).ok()
14233}
14234
14235fn unique_type_candidate_preserving_alias(
14236    analyzer: &CppGraphSource<'_>,
14237    file: &ProjectFile,
14238    candidates: &[&CodeUnit],
14239) -> Option<CodeUnit> {
14240    let first = *candidates.first()?;
14241    if declared_type_alias(analyzer, first) {
14242        return candidates
14243            .iter()
14244            .all(|candidate| {
14245                declared_type_alias(analyzer, candidate)
14246                    && candidate.kind() == first.kind()
14247                    && candidate.fq_name() == first.fq_name()
14248                    && candidate.source() == first.source()
14249            })
14250            .then(|| first.clone());
14251    }
14252    if analyzer.reference_uses_c_semantics(file)
14253        && first.is_class()
14254        && indexed_c_tag_kind(analyzer, first).is_some()
14255    {
14256        let mut full_source = None;
14257        let mut tag_kind = None;
14258        for candidate in candidates.iter().copied() {
14259            let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
14260            if tag_kind
14261                .replace(candidate_tag_kind)
14262                .is_some_and(|existing| existing != candidate_tag_kind)
14263            {
14264                return None;
14265            }
14266            if cpp_class_declaration_strength(analyzer, candidate)
14267                == CppClassDeclarationStrength::Full
14268                && full_source
14269                    .replace(candidate.source())
14270                    .is_some_and(|existing| existing != candidate.source())
14271            {
14272                return None;
14273            }
14274        }
14275    }
14276    candidates
14277        .iter()
14278        .all(|candidate| {
14279            !declared_type_alias(analyzer, candidate)
14280                && candidate.kind() == first.kind()
14281                && candidate.fq_name() == first.fq_name()
14282        })
14283        .then(|| first.clone())
14284}
14285
14286pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
14287    brokk_bifrost_core::analyzer::capabilities::provider_declares_type_alias(
14288        analyzer.type_alias_provider(),
14289        unit,
14290    )
14291}
14292
14293pub fn field_declared_type_binding(
14294    analyzer: &CppGraphSource<'_>,
14295    visibility: &VisibilityIndex<'_>,
14296    visible_from: &ProjectFile,
14297    field: &CodeUnit,
14298) -> Option<(String, Option<CodeUnit>, i32)> {
14299    let fact = visibility.field_declared_type_fact(analyzer, field)?;
14300    let normalized = normalize_field_type_text(&fact.type_text);
14301    let primary = visibility.resolve_unique_canonical_type_for_declaration(
14302        analyzer,
14303        visible_from,
14304        field,
14305        &normalized,
14306    );
14307    let resolved = match (primary, fact.template_arguments.as_deref()) {
14308        (Some(primary), Some(arguments)) => visibility
14309            .resolve_template_arguments(visible_from, primary, arguments)
14310            .ok(),
14311        (resolved, None) => resolved,
14312        (None, Some(_)) => None,
14313    };
14314    Some((normalized, resolved, fact.indirection))
14315}
14316
14317fn decode_field_declared_type_fact(
14318    analyzer: &CppGraphSource<'_>,
14319    field: &CodeUnit,
14320) -> Option<DeclaredFieldTypeFact> {
14321    if let Some(facts) = analyzer.declaration_source_properties(field)
14322        && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
14323        && facts
14324            .iter()
14325            .all(|fact| fact.field_type.as_ref() == Some(first))
14326    {
14327        return Some(first.clone());
14328    }
14329    let Some(declaration) = analyzer.get_source(field, false) else {
14330        return decode_indexed_field_declared_type_fact(analyzer, field);
14331    };
14332    let mut parser = Parser::new();
14333    parser
14334        .set_language(&tree_sitter_cpp::LANGUAGE.into())
14335        .ok()?;
14336    // A field's indexed source is stored without its surrounding class body.
14337    // Give tree-sitter that grammatical context before checking recovery-only
14338    // field shapes such as `PyObject_HEAD Imaging image;`.
14339    let contextual = parse_sentinel(
14340        "struct __bifrost_field_context { ",
14341        SentinelInput::region(&declaration),
14342        " };",
14343    );
14344    let (contextual_declaration, contextual_tree) = (contextual.source, contextual.tree);
14345    let mut stack = vec![contextual_tree.root_node()];
14346    while let Some(node) = stack.pop() {
14347        if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
14348            && node_text(recovered.name, &contextual_declaration) == field.identifier()
14349        {
14350            return Some(DeclaredFieldTypeFact {
14351                type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
14352                indirection: recovered.pointer_depth(),
14353                binds_indirectly: recovered.pointer_depth() > 0,
14354                template_arguments: None,
14355            });
14356        }
14357        if let Some(recovered) =
14358            recovered_function_like_field_declarator(node, &contextual_declaration)
14359            && node_text(recovered.name, &contextual_declaration) == field.identifier()
14360        {
14361            let type_node = node
14362                .child_by_field_name("type")
14363                .or_else(|| first_type_child(node))?;
14364            return Some(DeclaredFieldTypeFact {
14365                type_text: node_text(type_node, &contextual_declaration).to_string(),
14366                indirection: recovered.pointer_depth(),
14367                // A function-like declarator names a callable member, never a
14368                // subobject of the declared return type.
14369                binds_indirectly: true,
14370                template_arguments: cpp_template_reference_arguments(
14371                    type_node,
14372                    &contextual_declaration,
14373                ),
14374            });
14375        }
14376        if let Some(fact) =
14377            decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
14378        {
14379            return Some(fact);
14380        }
14381        let mut cursor = node.walk();
14382        stack.extend(node.named_children(&mut cursor));
14383    }
14384    let tree = parser.parse(&declaration, None)?;
14385    let mut stack = vec![tree.root_node()];
14386    while let Some(node) = stack.pop() {
14387        if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
14388        {
14389            return Some(fact);
14390        }
14391        let mut cursor = node.walk();
14392        stack.extend(node.named_children(&mut cursor));
14393    }
14394    None
14395}
14396
14397/// Decode a field whose generated owner prevents the ordinary source lookup
14398/// from selecting a standalone declaration. The indexed ranges still point
14399/// into the physical syntax tree, so recover the enclosing declaration from
14400/// that structure and apply the same declarator decoder to it.
14401fn decode_indexed_field_declared_type_fact(
14402    analyzer: &CppGraphSource<'_>,
14403    field: &CodeUnit,
14404) -> Option<DeclaredFieldTypeFact> {
14405    let cpp = analyzer.cpp?;
14406    let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
14407    let source = prepared.source();
14408    let root = prepared.tree().root_node();
14409    for range in analyzer.ranges(field) {
14410        let end = range.start_byte.saturating_add(1).min(source.len());
14411        let mut current = root.descendant_for_byte_range(range.start_byte, end);
14412        while let Some(node) = current {
14413            if matches!(node.kind(), "declaration" | "field_declaration")
14414                && let Some(fact) =
14415                    decode_declared_field_type_node(node, field.identifier(), source)
14416            {
14417                return Some(fact);
14418            }
14419            current = node.parent();
14420        }
14421    }
14422    None
14423}
14424
14425fn decode_declared_field_type_node(
14426    node: Node<'_>,
14427    field_name: &str,
14428    source: &str,
14429) -> Option<DeclaredFieldTypeFact> {
14430    if !matches!(node.kind(), "declaration" | "field_declaration") {
14431        return None;
14432    }
14433    let type_node = node
14434        .child_by_field_name("type")
14435        .or_else(|| first_type_child(node))?;
14436    cpp_declared_field_type_fact(node, type_node, field_name, source)
14437}
14438
14439pub(crate) fn cpp_declared_field_type_fact(
14440    node: Node<'_>,
14441    type_node: Node<'_>,
14442    field_name: &str,
14443    source: &str,
14444) -> Option<DeclaredFieldTypeFact> {
14445    let binding = declared_name_binding(node, type_node, field_name, source)?;
14446    let declared_type = if matches!(
14447        type_node.kind(),
14448        "class_specifier" | "struct_specifier" | "union_specifier"
14449    ) {
14450        type_node.child_by_field_name("name")
14451    } else {
14452        Some(type_node)
14453    };
14454    Some(DeclaredFieldTypeFact {
14455        type_text: declared_type.map_or_else(
14456            || field_name.to_string(),
14457            |declared_type| node_text(declared_type, source).to_string(),
14458        ),
14459        indirection: binding.pointer_depth,
14460        binds_indirectly: binding.indirect,
14461        template_arguments: declared_type
14462            .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
14463    })
14464}
14465
14466/// True when an alias declarator names a function type.
14467///
14468/// The declarator chain is walked through the `declarator` field, so the
14469/// parameter list -- a sibling field -- is never entered and a parameter's own
14470/// function declarator cannot be mistaken for the alias's.
14471pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
14472    let mut current = Some(declarator);
14473    while let Some(node) = current {
14474        match node.kind() {
14475            "function_declarator" | "abstract_function_declarator" => return true,
14476            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
14477                current = node.named_child(0);
14478            }
14479            _ => current = node.child_by_field_name("declarator"),
14480        }
14481    }
14482    false
14483}
14484
14485fn decode_structured_alias_target(
14486    analyzer: &CppGraphSource<'_>,
14487    unit: &CodeUnit,
14488) -> Option<StructuredAliasTarget> {
14489    let facts = analyzer.declaration_source_properties(unit)?;
14490    let first = facts.first()?.alias_target.as_ref()?;
14491    facts
14492        .iter()
14493        .all(|fact| fact.alias_target.as_ref() == Some(first))
14494        .then(|| first.clone())
14495}
14496
14497pub(crate) fn structured_alias_type_target(
14498    mut type_node: Node<'_>,
14499    source: &str,
14500) -> Option<StructuredAliasTarget> {
14501    while type_node.kind() == "type_descriptor" {
14502        type_node = type_node.child_by_field_name("type")?;
14503    }
14504    if type_node.kind() == "primitive_type" {
14505        return Some(StructuredAliasTarget::Builtin);
14506    }
14507    if matches!(
14508        type_node.kind(),
14509        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14510    ) {
14511        type_node = type_node.child_by_field_name("name")?;
14512    }
14513    let global = type_node.child_by_field_name("scope").is_none()
14514        && type_node.child(0).is_some_and(|child| child.kind() == "::");
14515    let mut components = Vec::new();
14516    append_structured_type_components(type_node, source, &mut components)?;
14517    let arguments = cpp_template_reference_arguments(type_node, source);
14518    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
14519        components,
14520        global,
14521        arguments,
14522    })
14523}
14524
14525fn append_structured_type_components(
14526    node: Node<'_>,
14527    source: &str,
14528    out: &mut Vec<String>,
14529) -> Option<()> {
14530    match node.kind() {
14531        "identifier" | "namespace_identifier" | "type_identifier" => {
14532            out.push(node_text(node, source).to_string());
14533            Some(())
14534        }
14535        "template_type" | "template_function" => {
14536            append_structured_type_components(node.child_by_field_name("name")?, source, out)
14537        }
14538        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
14539            let (scope, name) = qualified_name_parts(node)?;
14540            if let Some(scope) = scope {
14541                append_structured_type_components(scope, source, out)?;
14542            }
14543            append_structured_type_components(name, source, out)
14544        }
14545        _ => None,
14546    }
14547}
14548
14549/// How the declarator that names a declared entity binds the declaration's
14550/// type.
14551pub(crate) struct DeclaredNameBinding {
14552    /// Pointer declarators between the type and the name.
14553    pub pointer_depth: i32,
14554    /// The name binds the declared type through a pointer or a reference, so
14555    /// the declared entity holds no subobject of that type. An array of the
14556    /// type does hold subobjects, so it is not indirect.
14557    pub indirect: bool,
14558}
14559
14560pub(crate) fn declared_name_binding(
14561    declaration: Node<'_>,
14562    type_node: Node<'_>,
14563    field_name: &str,
14564    source: &str,
14565) -> Option<DeclaredNameBinding> {
14566    let mut stack = Vec::new();
14567    let mut cursor = declaration.walk();
14568    stack.extend(
14569        declaration
14570            .named_children(&mut cursor)
14571            .filter(|child| !same_node(*child, type_node)),
14572    );
14573    while let Some(node) = stack.pop() {
14574        if matches!(node.kind(), "identifier" | "field_identifier")
14575            && node_text(node, source) == field_name
14576        {
14577            let mut binding = DeclaredNameBinding {
14578                pointer_depth: 0,
14579                indirect: false,
14580            };
14581            let mut current = node.parent();
14582            while let Some(parent) = current {
14583                if same_node(parent, declaration) {
14584                    return Some(binding);
14585                }
14586                match parent.kind() {
14587                    "pointer_declarator" => {
14588                        binding.pointer_depth += 1;
14589                        binding.indirect = true;
14590                    }
14591                    "reference_declarator" => binding.indirect = true,
14592                    _ => {}
14593                }
14594                current = parent.parent();
14595            }
14596            return None;
14597        }
14598        let mut cursor = node.walk();
14599        stack.extend(node.named_children(&mut cursor));
14600    }
14601    None
14602}
14603
14604fn normalize_field_type_text(type_text: &str) -> String {
14605    const FIELD_SPECIFIERS: [&str; 8] = [
14606        "extern ",
14607        "static ",
14608        "mutable ",
14609        "constexpr ",
14610        "constinit ",
14611        "inline ",
14612        "volatile ",
14613        "const ",
14614    ];
14615
14616    let mut normalized = normalize_type_text(type_text);
14617    loop {
14618        let Some(stripped) = FIELD_SPECIFIERS
14619            .iter()
14620            .find_map(|specifier| normalized.strip_prefix(specifier))
14621        else {
14622            return normalized;
14623        };
14624        normalized = normalize_type_text(stripped);
14625    }
14626}
14627
14628pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
14629    let Some(type_node) = node.child_by_field_name("type") else {
14630        return false;
14631    };
14632    ctx.visibility.resolves_to_type(
14633        &ctx.analyzer,
14634        ctx.file,
14635        node_text(type_node, ctx.source),
14636        owner,
14637    )
14638}
14639
14640pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
14641    !ctx.analyzer
14642        .declarations(ctx.file)
14643        .into_iter()
14644        .filter(|unit| unit.is_function())
14645        .any(|unit| {
14646            ctx.analyzer.ranges(&unit).iter().any(|range| {
14647                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
14648            })
14649        })
14650}
14651
14652/// How a `T var ...;` declaration initializes its object.
14653pub enum DeclarationConstructorInitializer<'tree> {
14654    /// Direct initialization, `T var(args)` or `T var{args}`: the argument list
14655    /// the declaration hands the constructor.
14656    Arguments(Node<'tree>),
14657    /// Copy initialization from one expression, `T var = expr`, which supplies a
14658    /// single constructor argument without spelling an argument list.
14659    Expression(Node<'tree>),
14660    /// `T var;`, which names no constructor argument at all.
14661    Empty,
14662}
14663
14664pub fn declaration_constructor_initializer(
14665    node: Node<'_>,
14666) -> DeclarationConstructorInitializer<'_> {
14667    let mut cursor = node.walk();
14668    for child in node.named_children(&mut cursor) {
14669        if child.kind() == "init_declarator" {
14670            let Some(value) = child
14671                .child_by_field_name("value")
14672                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
14673                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
14674            else {
14675                return DeclarationConstructorInitializer::Empty;
14676            };
14677            return match value.kind() {
14678                "argument_list" | "initializer_list" => {
14679                    DeclarationConstructorInitializer::Arguments(value)
14680                }
14681                "compound_literal_expression" => call_arguments_node(value)
14682                    .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
14683                        DeclarationConstructorInitializer::Arguments(arguments)
14684                    }),
14685                _ => DeclarationConstructorInitializer::Expression(value),
14686            };
14687        }
14688        if let Some(declarator) = declaration_declarator(node, child) {
14689            return declarator_parameters(declarator)
14690                .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
14691                    DeclarationConstructorInitializer::Arguments(parameters)
14692                });
14693        }
14694    }
14695    DeclarationConstructorInitializer::Empty
14696}
14697
14698pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
14699    match declaration_constructor_initializer(node) {
14700        DeclarationConstructorInitializer::Arguments(arguments) => {
14701            argument_children(arguments).count()
14702        }
14703        DeclarationConstructorInitializer::Expression(_) => 1,
14704        DeclarationConstructorInitializer::Empty => 0,
14705    }
14706}
14707
14708/// The parameter list of the innermost declarator, which is where a
14709/// `T var(args)` declaration parsed as a function declarator keeps the
14710/// constructor arguments.
14711fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
14712    let mut current = node;
14713    loop {
14714        if let Some(parameters) = current.child_by_field_name("parameters") {
14715            return Some(parameters);
14716        }
14717        current = current.child_by_field_name("declarator")?;
14718    }
14719}
14720
14721fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
14722    brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(root, kind)
14723}
14724
14725fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
14726    if node.kind() == "identifier" {
14727        return true;
14728    }
14729    if node.kind() == "parenthesized_expression" {
14730        return false;
14731    }
14732    if node.kind() == "call_expression" {
14733        return node
14734            .child_by_field_name("function")
14735            .is_some_and(|function| function.kind() == "identifier");
14736    }
14737    let mut stack = vec![node];
14738    while let Some(descendant) = stack.pop() {
14739        if descendant != node && descendant.kind() == "parenthesized_expression" {
14740            continue;
14741        }
14742        if descendant.kind() == "identifier" {
14743            return true;
14744        }
14745        if descendant.kind() == "call_expression" {
14746            if descendant
14747                .child_by_field_name("function")
14748                .is_some_and(|function| function.kind() == "identifier")
14749            {
14750                return true;
14751            }
14752            continue;
14753        }
14754        push_named_children_reversed(descendant, &mut stack);
14755    }
14756    false
14757}
14758
14759fn macro_expansion_shape_is_safe(
14760    node: Node<'_>,
14761    source: &str,
14762    parameters: &[String],
14763    environment: &MacroEnvironment,
14764) -> bool {
14765    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
14766        return true;
14767    }
14768    if node.kind() == "call_expression" {
14769        let Some(function) = node.child_by_field_name("function") else {
14770            return true;
14771        };
14772        if function.kind() != "identifier" {
14773            return true;
14774        }
14775        let function_name = node_text(function, source);
14776        if parameters
14777            .iter()
14778            .any(|parameter| parameter == function_name)
14779        {
14780            return false;
14781        }
14782        if !environment.may_bind(function_name) {
14783            return true;
14784        }
14785        let Some(arguments) = node.child_by_field_name("arguments") else {
14786            return false;
14787        };
14788        return argument_children(arguments).all(|argument| {
14789            if argument.kind() == "identifier"
14790                && parameters
14791                    .iter()
14792                    .any(|parameter| parameter == node_text(argument, source))
14793            {
14794                return false;
14795            }
14796            macro_expansion_shape_is_safe(argument, source, parameters, environment)
14797        });
14798    }
14799    let mut stack = vec![node];
14800    while let Some(descendant) = stack.pop() {
14801        if descendant != node {
14802            if descendant.kind() == "parenthesized_expression" {
14803                continue;
14804            }
14805            if descendant.kind() == "call_expression" {
14806                let expands = descendant
14807                    .child_by_field_name("function")
14808                    .filter(|function| function.kind() == "identifier")
14809                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
14810                if expands {
14811                    return false;
14812                }
14813                continue;
14814            }
14815        }
14816        if descendant.kind() == "identifier" {
14817            let identifier = node_text(descendant, source);
14818            if parameters.iter().any(|parameter| parameter == identifier)
14819                || environment.may_bind(identifier)
14820            {
14821                return false;
14822            }
14823        }
14824        push_named_children_reversed(descendant, &mut stack);
14825    }
14826    true
14827}
14828
14829fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
14830    let source = prepared.source();
14831    let mut facts = Vec::new();
14832    let mut nodes = vec![prepared.tree().root_node()];
14833    while let Some(node) = nodes.pop() {
14834        if node.kind() == "preproc_include" {
14835            let Some(path) = node
14836                .child_by_field_name("path")
14837                .and_then(|path| crate::imports::include_path_literal_value(path, source))
14838                .map(str::to_owned)
14839            else {
14840                continue;
14841            };
14842            facts.push(StructuredIncludeFact {
14843                start_byte: node.start_byte(),
14844                end_byte: node.end_byte(),
14845                path,
14846            });
14847            continue;
14848        }
14849        push_named_children_reversed(node, &mut nodes);
14850    }
14851    Arc::from(facts.into_boxed_slice())
14852}
14853
14854fn has_unresolved_include_visible_before_in_prepared(
14855    file: &ProjectFile,
14856    prepared: &PreparedSyntaxTree,
14857    include_targets: &IncludeTargetIndex,
14858    facts: &[StructuredIncludeFact],
14859    before_byte: usize,
14860    effective_guards: &ReferenceGuardAugmenter<'_>,
14861) -> bool {
14862    let guards = OnceCell::new();
14863    let reference = CallableReferenceContext {
14864        effective_guards,
14865        file_guards: OnceCell::new(),
14866        file,
14867        position: Some(CallableReferencePosition {
14868            prepared,
14869            byte: before_byte,
14870            guards: &guards,
14871        }),
14872    };
14873    let root = prepared.tree().root_node();
14874    facts
14875        .iter()
14876        .filter(|fact| fact.end_byte <= before_byte)
14877        .any(|fact| {
14878            let node = root
14879                .descendant_for_byte_range(fact.start_byte, fact.end_byte)
14880                .expect("structured include fact range must be in prepared tree");
14881            assert_eq!(
14882                node.kind(),
14883                "preproc_include",
14884                "structured include fact range must identify its include node"
14885            );
14886            callable_preprocessor_context_is_visible_for_reference(
14887                node,
14888                prepared.source(),
14889                &reference,
14890            ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
14891        })
14892}
14893
14894fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
14895    let descendant = node;
14896    while let Some(parent) = node.parent() {
14897        if is_cpp_preprocessor_conditional(parent)
14898            && !is_file_covering_include_guard(parent, source)
14899            && preprocessor_conditional_contains_descendant(parent, descendant)
14900        {
14901            return true;
14902        }
14903        node = parent;
14904    }
14905    false
14906}
14907
14908/// The [`OwningPreprocessorConditionals`] of a macro event at `event`.
14909///
14910/// The descendant this walks up from is the one
14911/// [`VisibilityIndex::macro_event_condition_value`] starts from -- the
14912/// innermost node at the event's first byte, not the event node itself --
14913/// because containment compares that descendant's end against the recovered
14914/// conditional boundary, and the two nodes end in different places. An event
14915/// that [`has_preprocessor_conditional_ancestor`] rejects owns nothing: that
14916/// predicate is what has always decided whether an event is conditional at
14917/// all, and answering it first also skips the walk for the ordinary
14918/// unconditional event.
14919fn owning_preprocessor_conditionals(
14920    root: Node<'_>,
14921    event: Node<'_>,
14922    source: &str,
14923) -> OwningPreprocessorConditionals {
14924    if !has_preprocessor_conditional_ancestor(event, source) {
14925        return OwningPreprocessorConditionals::default();
14926    }
14927    let start = event.start_byte();
14928    let descendant = root
14929        .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
14930        .expect("a byte inside the parsed tree names a descendant");
14931    let mut owners = Vec::new();
14932    let mut current = descendant.parent();
14933    while let Some(conditional) = current {
14934        if is_cpp_preprocessor_conditional(conditional)
14935            && !is_file_covering_include_guard(conditional, source)
14936            && preprocessor_conditional_contains_descendant(conditional, descendant)
14937        {
14938            owners.push(conditional.start_byte());
14939        }
14940        current = conditional.parent();
14941    }
14942    owners.into_boxed_slice()
14943}
14944
14945fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
14946    node.parent()
14947        .filter(|parent| parent.kind() == "translation_unit")
14948        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
14949        && is_canonical_include_guard(node, source)
14950}
14951
14952fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
14953    if node.kind() != "preproc_ifdef"
14954        || node
14955            .child(0)
14956            .is_none_or(|directive| directive.kind() != "#ifndef")
14957        || node.child_by_field_name("alternative").is_some()
14958    {
14959        return false;
14960    }
14961    let Some(guard_name) = node.child_by_field_name("name") else {
14962        return false;
14963    };
14964    let mut cursor = node.walk();
14965    node.named_children(&mut cursor)
14966        .find(|child| *child != guard_name && child.kind() != "comment")
14967        .filter(|child| child.kind() == "preproc_def")
14968        .and_then(|definition| definition.child_by_field_name("name"))
14969        .is_some_and(|defined_name| {
14970            node_text(defined_name, source) == node_text(guard_name, source)
14971        })
14972}
14973
14974fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14975    let mut guard = None;
14976    for child in named_children_iter(root) {
14977        if child.kind() == "comment" || is_pragma_once(child, source) {
14978            continue;
14979        }
14980        if guard.is_none() && is_canonical_include_guard(child, source) {
14981            guard = Some(child);
14982        } else {
14983            return None;
14984        }
14985    }
14986    guard
14987        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14988        .map(|name| node_text(name, source).to_string())
14989}
14990
14991fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14992    if (0..root.named_child_count())
14993        .filter_map(|index| root.named_child(index))
14994        .any(|child| is_pragma_once(child, source))
14995    {
14996        return MacroIncludeProtection::PragmaOnce;
14997    }
14998    top_level_canonical_include_guard_name(root, source)
14999        .map(MacroIncludeProtection::MacroGuard)
15000        .unwrap_or(MacroIncludeProtection::None)
15001}
15002
15003fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
15004    node.kind() == "preproc_call"
15005        && node
15006            .child_by_field_name("directive")
15007            .is_some_and(|directive| node_text(directive, source) == "#pragma")
15008        && node
15009            .child_by_field_name("argument")
15010            .is_some_and(|argument| node_text(argument, source).trim() == "once")
15011}
15012
15013fn parse_preproc_identifier(argument: &str) -> Option<String> {
15014    let parsed = parse_sentinel(
15015        "void __bifrost_undef() { ",
15016        SentinelInput::replacement(argument),
15017        "; }",
15018    );
15019    let (sentinel, tree) = (parsed.source, parsed.tree);
15020    if tree.root_node().has_error() {
15021        return None;
15022    }
15023    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
15024    // A line comment before the synthetic terminator is a named extra, not
15025    // another expression in the directive argument.
15026    let identifier = single_named_child_matching(statement, |child| !child.is_extra())?;
15027    (identifier.kind() == "identifier").then(|| node_text(identifier, &sentinel).to_string())
15028}
15029
15030pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
15031    match node.kind() {
15032        "identifier" | "field_identifier" => {
15033            let name = node_text(node, source).trim();
15034            (!name.is_empty()).then(|| name.to_string())
15035        }
15036        "abstract_array_declarator"
15037        | "abstract_function_declarator"
15038        | "abstract_parenthesized_declarator"
15039        | "abstract_pointer_declarator"
15040        | "abstract_reference_declarator" => None,
15041        "function_declarator" => node
15042            .child_by_field_name("declarator")
15043            .or_else(|| node.child_by_field_name("name"))
15044            .and_then(|child| extract_variable_name(child, source)),
15045        _ => node
15046            .child_by_field_name("declarator")
15047            .or_else(|| node.child_by_field_name("name"))
15048            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
15049            .and_then(|child| extract_variable_name(child, source)),
15050    }
15051}
15052
15053/// Whether `file` is proven to use plain-C source semantics.
15054///
15055/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
15056/// compilation dialect on their own, so only an exact `.c` source extension is
15057/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
15058/// identifiers.
15059///
15060/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
15061/// which extraction reads too (a `.c` file is extracted with C tag scope), so
15062/// the doctrine has exactly one definition.
15063pub fn is_c_source_file(file: &ProjectFile) -> bool {
15064    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
15065}
15066
15067/// Whether tree-sitter parsed the operand of C `sizeof(T)` as an expression
15068/// identifier even though `T` may denote a typedef.
15069///
15070/// The grammar cannot distinguish `sizeof(value)` from `sizeof(Type)` without
15071/// semantic information. Keep this helper structural and narrow; callers must
15072/// still prove a visible type and reject an active ordinary-namespace shadow.
15073pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
15074    if !is_c_source_file(file) || node.kind() != "identifier" {
15075        return false;
15076    }
15077    let mut operand = node;
15078    while let Some(parent) = operand.parent().filter(|parent| {
15079        parent.kind() == "parenthesized_expression"
15080            && parent.named_child_count() == 1
15081            && parent.named_child(0) == Some(operand)
15082    }) {
15083        operand = parent;
15084    }
15085    operand.parent().is_some_and(|parent| {
15086        parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
15087    })
15088}
15089
15090/// Return the type and member leaves of a C `offsetof` member designator.
15091///
15092/// `offsetof_expression` is a dedicated tree-sitter node, so its two operands
15093/// must be interpreted through their named fields.  In particular, do not
15094/// infer the aggregate from the enclosing lexical scope: an `offsetof` can
15095/// name a member of an unrelated aggregate, including a field promoted from
15096/// an anonymous union.  A missing or unsupported operand is deliberately
15097/// rejected so callers can keep the reference unresolved.
15098pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
15099    if !is_c_offsetof_member_node(node) {
15100        return None;
15101    }
15102    let expression = node.parent()?;
15103    if expression.has_error() {
15104        return None;
15105    }
15106    let closing = expression.child(expression.child_count().saturating_sub(1))?;
15107    if closing.kind() != ")" || closing.is_missing() {
15108        return None;
15109    }
15110    let type_descriptor = expression.child_by_field_name("type")?;
15111    if type_descriptor.kind() != "type_descriptor"
15112        || type_descriptor.is_missing()
15113        || type_descriptor.has_error()
15114    {
15115        return None;
15116    }
15117    let type_specifier = type_descriptor.child_by_field_name("type")?;
15118    if type_specifier.is_missing() || type_specifier.has_error() {
15119        return None;
15120    }
15121    let type_reference = match type_specifier.kind() {
15122        "class_specifier" | "struct_specifier" | "union_specifier" => {
15123            type_specifier.child_by_field_name("name")?
15124        }
15125        _ => type_specifier,
15126    };
15127    (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
15128}
15129
15130/// Whether `node` is the member leaf of an `offsetof_expression`, including a
15131/// malformed type operand.  Callers use this guard to prevent the ordinary
15132/// field-name heuristics from guessing an owner after structured resolution
15133/// has failed.
15134pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
15135    node.kind() == "field_identifier"
15136        && node.parent().is_some_and(|parent| {
15137            parent.kind() == "offsetof_expression"
15138                && parent.child_by_field_name("member") == Some(node)
15139        })
15140}
15141
15142/// Admit an ABI 15 template-function shape to type resolution only through
15143/// the structured argument list of a type descriptor. This is syntax evidence;
15144/// callers must still resolve a visible type binding before proving identity.
15145pub fn cpp_template_function_has_type_owner(node: Node<'_>) -> bool {
15146    if node.kind() != "template_function" {
15147        return false;
15148    }
15149    let Some(arguments) = node
15150        .parent()
15151        .filter(|parent| parent.kind() == "template_argument_list")
15152    else {
15153        return false;
15154    };
15155    let Some(owner) = arguments.parent().filter(|parent| {
15156        parent.kind() == "template_type"
15157            && parent.child_by_field_name("arguments") == Some(arguments)
15158    }) else {
15159        return false;
15160    };
15161
15162    let mut current = owner;
15163    while let Some(parent) = current.parent() {
15164        if parent.kind() == "type_descriptor" && parent.child_by_field_name("type") == Some(current)
15165        {
15166            return true;
15167        }
15168        if parent.kind() == "qualified_identifier"
15169            && parent.child_by_field_name("name") == Some(current)
15170        {
15171            current = parent;
15172            continue;
15173        }
15174        return false;
15175    }
15176    false
15177}
15178
15179/// Whether `node` is a template argument name that tree-sitter spelled with
15180/// type syntax.
15181///
15182/// The grammar cannot tell a type argument from a non-type (value) argument, so
15183/// it gives both the same shape:
15184/// `template_argument_list -> type_descriptor -> type_identifier`. In
15185/// `std::array<W, N>` the type `W` and the constant `N` parse identically, and
15186/// so do `std::span<const uint8_t, ED448_LEN>`'s length and a nested type
15187/// member used as a real type argument.
15188///
15189/// This helper reports only the syntactic position. A caller must still prove
15190/// which namespace explains the spelling: forward navigation asks the type
15191/// namespace first and reads the leaf as a value only when no type explains it,
15192/// and the inverse field scan admits the leaf only when no visible type does
15193/// (#2556).
15194pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
15195    if node.kind() != "type_identifier" {
15196        return false;
15197    }
15198    let Some(descriptor) = node
15199        .parent()
15200        .filter(|parent| parent.kind() == "type_descriptor")
15201    else {
15202        return false;
15203    };
15204    if descriptor.child_by_field_name("type") != Some(node) {
15205        return false;
15206    }
15207    let Some(arguments) = descriptor
15208        .parent()
15209        .filter(|parent| parent.kind() == "template_argument_list")
15210    else {
15211        return false;
15212    };
15213    arguments.parent().is_some_and(|owner| {
15214        matches!(
15215            owner.kind(),
15216            "template_type" | "template_function" | "template_method"
15217        ) && owner.child_by_field_name("arguments") == Some(arguments)
15218    })
15219}
15220
15221/// Whether a reference written in `file` reads C++ source with C semantics.
15222///
15223/// [`is_c_source_file`] answers the half a path settles on its own. The other
15224/// half is a header, which has no dialect of its own: it is read as C exactly
15225/// when every workspace translation unit that provably compiles it compiles it
15226/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
15227///
15228/// This is the gate for anything that is really about the compilation
15229/// language of the code being read -- which reading of an included header's
15230/// declarations is in scope, whether `this` is an ordinary identifier. It is
15231/// NOT the gate for a question that is genuinely about a `.c` file on disk;
15232/// those keep calling [`is_c_source_file`].
15233pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
15234    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
15235}
15236
15237pub fn is_declarator_node(node: Node<'_>) -> bool {
15238    matches!(
15239        node.kind(),
15240        "identifier"
15241            | "field_identifier"
15242            | "qualified_identifier"
15243            | "scoped_identifier"
15244            | "pointer_declarator"
15245            | "reference_declarator"
15246            | "array_declarator"
15247            | "parenthesized_declarator"
15248            | "function_declarator"
15249    )
15250}
15251
15252/// Returns the declarator represented by one direct child of a declaration.
15253///
15254/// Tree-sitter exposes the first declarator through the `declarator` field,
15255/// but subsequent comma-separated declarators are unfielded direct children:
15256/// `int first, second;` therefore has an `identifier` child for `first` and a
15257/// second, otherwise identical, `identifier` child for `second`. Consumers
15258/// must inspect both shapes or a later declarator can be mistaken for a use of
15259/// an unrelated indexed symbol.
15260pub fn declaration_declarator<'tree>(
15261    declaration: Node<'tree>,
15262    child: Node<'tree>,
15263) -> Option<Node<'tree>> {
15264    if !matches!(
15265        declaration.kind(),
15266        "declaration"
15267            | "field_declaration"
15268            | "parameter_declaration"
15269            | "optional_parameter_declaration"
15270            | "function_definition"
15271            | "type_definition"
15272            | "alias_declaration"
15273            | "template_instantiation"
15274    ) {
15275        return None;
15276    }
15277    if declaration
15278        .child_by_field_name("type")
15279        .is_some_and(|type_node| same_node(type_node, child))
15280    {
15281        return None;
15282    }
15283    if child.kind() == "init_declarator" {
15284        return child.child_by_field_name("declarator");
15285    }
15286    let field = field_name_in_parent(declaration, child);
15287    if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
15288        || (declaration.kind() == "type_definition"
15289            && child.kind() == "type_identifier"
15290            && matches!(field, Some("declarator") | None))
15291    {
15292        Some(child)
15293    } else {
15294        None
15295    }
15296}
15297
15298/// Read the name, declarator, and optional type of a typed local binding.
15299pub fn cpp_binding_parts<'tree>(
15300    node: Node<'tree>,
15301    source: &str,
15302) -> Option<(String, Node<'tree>, Option<Node<'tree>>)> {
15303    let declarator = node.child_by_field_name("declarator")?;
15304    let name = extract_variable_name(declarator, source)?;
15305    let type_node = node
15306        .child_by_field_name("type")
15307        .or_else(|| first_type_child(node));
15308    Some((name, declarator, type_node))
15309}
15310
15311/// One run of a container's children whose parsed namespaces differ from
15312/// their lexical namespaces, with the complete lexical namespace path.
15313#[derive(Clone, Debug, PartialEq, Eq)]
15314pub struct RecoveredNamespaceRegion {
15315    /// Start byte of the first child in the run.
15316    pub start: usize,
15317    /// End byte of the last child in the run.
15318    pub end: usize,
15319    /// The complete enclosing namespace path of the run, outermost first.
15320    /// This can be empty when a parsed namespace extends past its real close.
15321    pub components: Vec<String>,
15322    /// The `namespace Name { ... }` construct that opens each of `components`,
15323    /// parallel to it, from the `namespace` keyword through the close the index
15324    /// proved for its `{`. A proven surviving namespace definition uses its
15325    /// body's direct close; a recovered head, or a namespace body exposed by
15326    /// malformed member recovery, uses the file-global brace stack. A level
15327    /// whose close the index could not prove -- the file never closes it, or
15328    /// the file's brace token stream lost an opening token (#3328) -- carries
15329    /// the head alone. This is the range the declaration walk records for the
15330    /// Module, so a namespace whose head collapsed into an `ERROR` reports
15331    /// where it is written rather than where its first member is (#3309).
15332    pub component_ranges: Vec<Range>,
15333    /// The name token each of `components` is written as, parallel to it. The
15334    /// declaration walk interns this subspan as the Module's name occurrence:
15335    /// the node the walk is visiting when it declares the level belongs to an
15336    /// unrelated construct inside the region, and deriving the name from it
15337    /// paired the level's range with that construct's name token (#4292).
15338    pub component_name_ranges: Vec<Range>,
15339    /// The close the index proved for each level's `{`, parallel to
15340    /// `components`. A node after that close is outside the level (#3633).
15341    /// `None` when the file never closes the level or the walk proved no
15342    /// pairing in the file (#3328); the level then applies to the whole region
15343    /// as a best-effort scope.
15344    pub component_closes: Vec<Option<Range>>,
15345}
15346
15347/// The namespaces C++ parse recovery drops from a file's tree.
15348///
15349/// When tree-sitter cannot parse a construct inside a namespace body it closes
15350/// an inner scope with a MISSING brace, or skips an opening brace into an
15351/// ERROR node. Every real `}` after that then closes one scope too early: a
15352/// class body's `}` closes the namespace, the namespace's own `}` closes its
15353/// parent, and the outermost real closes land in a trailing ERROR node. The
15354/// declarations between a stolen close and the real one keep their byte
15355/// positions but lose their `namespace_definition` ancestors (Catch2's
15356/// `catch_matchers_templated.hpp`, issue #1537).
15357///
15358/// A file-global stack over real AST brace tokens restores both lost and
15359/// overextended namespaces. Missing braces do nothing; error-free subtrees are
15360/// balanced and can be skipped. For each child of a damaged container, compare
15361/// the namespaces still open on that stack with the parsed ancestor path. A
15362/// difference yields a region carrying the complete lexical path, including
15363/// an empty path when recovery swallowed file-scope declarations. Consecutive
15364/// children sharing that path form one [`RecoveredNamespaceRegion`]. The same
15365/// pass retains matching brace positions for declaration partitioning (#3087).
15366/// A file without parse errors needs neither correction nor a brace index.
15367///
15368/// The stack restores the source's nesting only while the tree's brace tokens
15369/// are the source's braces. Severe recovery can break that even when the real
15370/// brace counts balance: in simdjson's `compile_time_json-inl.h`, ABI 15 keeps
15371/// the reflection case labels as `character` nodes but loses the outer
15372/// namespace's closing token. The global stack then cascades the function and
15373/// inner namespace closes into the wrong owners. A surviving
15374/// `namespace_definition` can supply stronger provenance when its body has a
15375/// genuine declaration boundary: its direct real `}` belongs to that
15376/// namespace, while a missing direct close proves that the namespace has no
15377/// close to claim. A body whose direct boundary is itself recovered from a
15378/// class/member error stays with the global fallback. The proven boundaries
15379/// override the global stack only when the file has no unpaired real close. An
15380/// unpaired close proves the stream lost an open at a position the stack cannot
15381/// recover, so all pairing in that file remains invalid:
15382/// [`Self::matching_close_brace`] then answers nothing and each restored level
15383/// keeps the head that writes it. The namespace paths remain, as the
15384/// best-effort structure recovery they are.
15385///
15386/// Every consumer reads a node's namespace path through one authority rule,
15387/// shared by [`Self::enclosing_namespace_levels`] and
15388/// [`Self::restore_enclosing_namespaces`], so declaration collection and both
15389/// lookup directions agree:
15390///
15391/// - Outside every region, the node's parsed `namespace_definition` chain is
15392///   the whole answer.
15393/// - Inside a region, the region's restored levels come first and only the
15394///   parsed ancestors that start inside the region follow them. The region
15395///   overrides a parsed ancestor that starts before it, which is how a
15396///   namespace the tree kept open past its real close gives up the
15397///   declarations after that close (#3087).
15398/// - A restored level stops applying at the close the stack paired with its
15399///   `{`, and so does every level inside it. The walk compares a child with
15400///   the stack before it descends into that child, so a run can reach past a
15401///   close inside its last child. In simdjson's `tests/dom/basictests.cpp` the
15402///   collapsed `namespace type_tests {` on line 1467 closes on line 1811, and
15403///   recovery put that `}` inside a `preproc_else` that runs to line 2505: the
15404///   region that restores `type_tests` covers the whole `preproc_else`, and
15405///   without this check every namespace written after the close inherited
15406///   `type_tests` (#3633). A level whose close the stack did not prove applies
15407///   to the whole region, the best-effort answer #3328 leaves.
15408/// - A region whose restored path is empty keeps exactly the parsed ancestors
15409///   whose own `{` and `}` are both real tokens, wherever they start, and drops
15410///   the others. This holds whether or not the file's pairing is proven.
15411#[derive(Clone, Debug, Default)]
15412pub struct OrphanedNamespaceScopeIndex {
15413    regions: Vec<RecoveredNamespaceRegion>,
15414    /// Open-brace start byte to the close the file-global stack paired with it.
15415    /// Empty when the walk proved the brace token stream incomplete.
15416    brace_closes: HashMap<usize, Range>,
15417}
15418
15419impl OrphanedNamespaceScopeIndex {
15420    pub fn build(root: Node<'_>, source: &str) -> Self {
15421        if !root.has_error() {
15422            return Self::default();
15423        }
15424        /// One namespace level open on the brace stack: the `namespace Name {`
15425        /// head it is written as, and the `{`'s start byte, which is the
15426        /// `brace_closes` key its close lands under. The close is not known
15427        /// while the level is open, so the final ranges are assembled once the
15428        /// walk has paired every brace.
15429        #[derive(Clone, Copy)]
15430        struct OpenHead {
15431            head: Range,
15432            open: usize,
15433        }
15434        /// A [`RecoveredNamespaceRegion`] whose levels still name their open
15435        /// braces rather than their closes.
15436        struct PendingRegion {
15437            start: usize,
15438            end: usize,
15439            components: Vec<String>,
15440            heads: Vec<OpenHead>,
15441            name_ranges: Vec<Range>,
15442        }
15443        struct Frame<'tree> {
15444            node: Node<'tree>,
15445            children: Vec<Node<'tree>>,
15446            next: usize,
15447            parsed_scope: Vec<String>,
15448            run: Option<PendingRegion>,
15449        }
15450        fn frame<'tree>(
15451            node: Node<'tree>,
15452            mut parsed_scope: Vec<String>,
15453            source: &str,
15454        ) -> Frame<'tree> {
15455            if node.kind() == "namespace_definition"
15456                && let Some(name) = node.child_by_field_name("name")
15457            {
15458                let mut components = Vec::new();
15459                if append_cpp_name_components(name, source, &mut components).is_some() {
15460                    parsed_scope.extend(components);
15461                }
15462            }
15463            let mut cursor = node.walk();
15464            Frame {
15465                node,
15466                children: node.children(&mut cursor).collect(),
15467                next: 0,
15468                parsed_scope,
15469                run: None,
15470            }
15471        }
15472        let mut regions = Vec::new();
15473        let mut brace_closes = HashMap::default();
15474        // A surviving namespace_definition may own a direct brace boundary of
15475        // its declaration_list. Keep that evidence separate from the file-
15476        // global stack: recovery can lose an outer namespace close while
15477        // retaining balanced braces from nested constructs, so token order
15478        // alone can assign a real close to the wrong namespace. The helper
15479        // rejects body boundaries exposed by malformed class/member recovery.
15480        let mut namespace_brace_proofs = Vec::new();
15481        // Real closes that arrived with nothing open. Each one proves the tree
15482        // is missing a scope-opening token the source writes, which is what
15483        // makes every pairing in the file unusable as an extent (#3328). The
15484        // complete set is kept, not a count, so a walk that has to be diagnosed
15485        // shows every position the stream went out of sync.
15486        let mut unpaired_closes: Vec<Range> = Vec::new();
15487        // The brace stack belongs to the file, not to a parser frame. A real
15488        // close inside a damaged child can close its parent's namespace. The
15489        // following children must see that removal immediately (#3087).
15490        let mut open = Vec::new();
15491        let mut lexical_scope = Vec::new();
15492        // One head per entry of `lexical_scope`: `namespace a::b {` opens two
15493        // levels and both are written by that one head, exactly as the
15494        // ordinary namespace visit records one range for every level of the
15495        // C++17 shorthand (#1878).
15496        let mut lexical_heads: Vec<OpenHead> = Vec::new();
15497        // One name token per entry of `lexical_scope`: unlike the shared head,
15498        // each level of a `namespace a::b {` shorthand has its own token.
15499        let mut lexical_name_ranges: Vec<Range> = Vec::new();
15500        let mut frames = vec![frame(root, Vec::new(), source)];
15501        // A frame's node is the previous frame's direct child, so the frame
15502        // below answers what Node::parent would without re-descending from the
15503        // root; per-node parent or sibling climbs are quadratic over a wide
15504        // translation unit (#3141).
15505        while !frames.is_empty() {
15506            let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
15507            let current = frames.last_mut().expect("frames is non-empty");
15508            if current.next == current.children.len() {
15509                regions.extend(frames.pop().expect("the frame just borrowed").run);
15510                continue;
15511            }
15512            let child = current.children[current.next];
15513            current.next += 1;
15514            match child.kind() {
15515                "{" if !child.is_missing() => {
15516                    regions.extend(current.run.take());
15517                    if let Some(namespace) = parent_node.filter(|parent| {
15518                        parent.kind() == "namespace_definition"
15519                            && parent.child_by_field_name("body") == Some(current.node)
15520                            && child.start_byte() == current.node.start_byte()
15521                    }) && let Some(proof) = namespace_body_brace_proof(current.node)
15522                    {
15523                        namespace_brace_proofs.push((child.start_byte(), namespace, proof));
15524                    }
15525                    // The head is the construct that writes the namespace: the
15526                    // `namespace_definition` node when one survived, and the
15527                    // bare `namespace` keyword when recovery collapsed the head
15528                    // into flat `ERROR` children (#3084).
15529                    let mut named = parent_node.and_then(|parent| {
15530                        namespace_body_name_components(parent, current.node, source)
15531                    });
15532                    if named.is_none() {
15533                        named = recovered_namespace_open_components(
15534                            &current.children[..current.next - 1],
15535                            source,
15536                        );
15537                    }
15538                    open.push((child.start_byte(), lexical_scope.len()));
15539                    if let Some((written_at, components)) = named {
15540                        let head = OpenHead {
15541                            head: Range {
15542                                start_byte: written_at.start_byte(),
15543                                end_byte: child.end_byte(),
15544                                start_line: written_at.start_position().row + 1,
15545                                end_line: child.end_position().row + 1,
15546                            },
15547                            open: child.start_byte(),
15548                        };
15549                        lexical_heads.extend(std::iter::repeat_n(head, components.len()));
15550                        lexical_name_ranges
15551                            .extend(components.iter().map(|(_, name_range)| *name_range));
15552                        lexical_scope
15553                            .extend(components.into_iter().map(|(component, _)| component));
15554                    }
15555                    continue;
15556                }
15557                "}" if !child.is_missing() => {
15558                    regions.extend(current.run.take());
15559                    let close = Range {
15560                        start_byte: child.start_byte(),
15561                        end_byte: child.end_byte(),
15562                        start_line: child.start_position().row + 1,
15563                        end_line: child.end_position().row + 1,
15564                    };
15565                    match open.pop() {
15566                        Some((start, namespace_len)) => {
15567                            debug_assert!(
15568                                start < close.start_byte,
15569                                "a close pairs with an open before it: {start} then {close:?}"
15570                            );
15571                            lexical_scope.truncate(namespace_len);
15572                            lexical_heads.truncate(namespace_len);
15573                            lexical_name_ranges.truncate(namespace_len);
15574                            brace_closes.insert(start, close);
15575                        }
15576                        None => unpaired_closes.push(close),
15577                    }
15578                    continue;
15579                }
15580                _ => {}
15581            }
15582            // The namespace head and body precede their opening brace; their
15583            // own parsed namespace is not yet on the lexical stack. Compare
15584            // the items inside the body, not this header bookkeeping.
15585            if current.node.kind() != "namespace_definition"
15586                && lexical_scope != current.parsed_scope
15587            {
15588                match &mut current.run {
15589                    Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
15590                    run => {
15591                        regions.extend(run.take());
15592                        *run = Some(PendingRegion {
15593                            start: child.start_byte(),
15594                            end: child.end_byte(),
15595                            components: lexical_scope.clone(),
15596                            heads: lexical_heads.clone(),
15597                            name_ranges: lexical_name_ranges.clone(),
15598                        });
15599                    }
15600                }
15601            } else {
15602                regions.extend(current.run.take());
15603            }
15604            // Error-free subtrees are balanced. Keeping them intact also
15605            // leaves their valid nested namespaces to the ordinary ancestry
15606            // walk in restore_enclosing_namespaces. A clean
15607            // namespace-sentinel wrapper (the macro-declaration grammar reads
15608            // it as a function_definition whose type is the literal namespace
15609            // keyword) is the one exception: its body is a statement-context
15610            // compound whose members need the recovered scope.
15611            let descend = child.has_error()
15612                || (child.kind() == "function_definition"
15613                    && child.child_by_field_name("body").is_some_and(|body| {
15614                        namespace_body_name_components(child, body, source).is_some()
15615                    }));
15616            if descend {
15617                let parsed_scope = current.parsed_scope.clone();
15618                frames.push(frame(child, parsed_scope, source));
15619            }
15620        }
15621        // A close with nothing open is only possible when a `{` the source
15622        // writes is not a `{` token of the tree. The stack then paired some
15623        // earlier open with a close that belongs to an inner scope, and
15624        // nothing in the token stream says which pairs absorbed the loss: the
15625        // lost token sits at an unknown position before the close that
15626        // exposed it. Keep no pairing rather than an unprovable extent. An
15627        // open the file never closes is the opposite case and stays: it makes
15628        // no claim, so the levels it writes keep their heads either way.
15629        if !unpaired_closes.is_empty() {
15630            brace_closes.clear();
15631        } else {
15632            // Use AST ownership when the global walk has no unpaired close. A missing
15633            // direct close removes a misleading stack pairing; a real direct
15634            // close replaces any cascade from a damaged descendant. Apply this
15635            // only to the namespace chain containing a proven missing boundary;
15636            // unrelated or ambiguous namespaces retain the global fallback.
15637            let missing_namespaces = namespace_brace_proofs
15638                .iter()
15639                .filter_map(|(_, namespace, proof)| {
15640                    matches!(proof, NamespaceBodyBraceProof::Missing).then_some(namespace.id())
15641                })
15642                .collect::<HashSet<_>>();
15643            for (open, namespace, proof) in namespace_brace_proofs {
15644                if !std::iter::successors(Some(namespace), |node| node.parent())
15645                    .any(|ancestor| missing_namespaces.contains(&ancestor.id()))
15646                {
15647                    continue;
15648                }
15649                match proof {
15650                    NamespaceBodyBraceProof::Real(close) => {
15651                        brace_closes.insert(open, close);
15652                    }
15653                    NamespaceBodyBraceProof::Missing => {
15654                        brace_closes.remove(&open);
15655                    }
15656                }
15657            }
15658        }
15659        // Every surviving brace is paired now, so each level's head can grow to
15660        // the construct it writes. A level whose `{` never closes keeps its
15661        // head: the file ends inside the namespace and there is no real close
15662        // to reach for.
15663        let regions = regions
15664            .into_iter()
15665            .map(|region| {
15666                debug_assert_eq!(
15667                    region.components.len(),
15668                    region.heads.len(),
15669                    "every recovered namespace level carries the head that writes it: {:?}",
15670                    region.components
15671                );
15672                debug_assert_eq!(
15673                    region.components.len(),
15674                    region.name_ranges.len(),
15675                    "every recovered namespace level carries the name token that writes it: {:?}",
15676                    region.components
15677                );
15678                let component_closes: Vec<_> = region
15679                    .heads
15680                    .iter()
15681                    .map(|level| brace_closes.get(&level.open).copied())
15682                    .collect();
15683                let component_ranges = region
15684                    .heads
15685                    .iter()
15686                    .zip(&component_closes)
15687                    .map(|(level, close)| match close {
15688                        Some(close) => Range {
15689                            start_byte: level.head.start_byte,
15690                            end_byte: close.end_byte,
15691                            start_line: level.head.start_line,
15692                            end_line: close.end_line,
15693                        },
15694                        None => level.head,
15695                    })
15696                    .collect();
15697                RecoveredNamespaceRegion {
15698                    start: region.start,
15699                    end: region.end,
15700                    components: region.components,
15701                    component_ranges,
15702                    component_name_ranges: region.name_ranges,
15703                    component_closes,
15704                }
15705            })
15706            .collect();
15707        Self {
15708            regions,
15709            brace_closes,
15710        }
15711    }
15712
15713    /// The real AST brace matching an opening brace in a damaged subtree.
15714    /// Missing tokens and braces inside comments or literals do not participate.
15715    ///
15716    /// `None` when this open has no close in the file, and for every open in a
15717    /// file whose brace token stream lost an opening token: a caller cannot
15718    /// treat a pairing the walk could not prove as an enclosing extent (#3328).
15719    pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
15720        self.brace_closes.get(&open).copied()
15721    }
15722
15723    pub fn is_empty(&self) -> bool {
15724        self.regions.is_empty()
15725    }
15726
15727    /// The bytes this index holds, for the analyzer cache's weight.
15728    pub fn approximate_size(&self) -> usize {
15729        self.regions.iter().fold(
15730            self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
15731            |total, region| {
15732                total
15733                    .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
15734                    .saturating_add(region.components.iter().map(String::len).sum::<usize>())
15735                    .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
15736                    .saturating_add(
15737                        region.component_name_ranges.len() * std::mem::size_of::<Range>(),
15738                    )
15739                    .saturating_add(
15740                        region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
15741                    )
15742            },
15743        )
15744    }
15745
15746    /// The innermost recovered region containing `byte`.
15747    pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
15748        self.regions
15749            .iter()
15750            .filter(|region| region.start <= byte && byte < region.end)
15751            .min_by_key(|region| region.end - region.start)
15752    }
15753
15754    /// The enclosing namespaces of `node`, outermost first, restoring the ones
15755    /// parse recovery dropped from its ancestor chain. The one answer both
15756    /// lookup directions and declaration collection use (issue #1537).
15757    pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
15758        self.enclosing_namespace_levels(node, source)
15759            .into_iter()
15760            .map(|(component, ..)| component)
15761            .collect()
15762    }
15763
15764    /// [`Self::enclosing_namespace_components`] with the construct that writes
15765    /// each level and the level's own name token, for the declaration walk
15766    /// that has to record both ranges.
15767    ///
15768    /// A level the brace stack restored carries the ranges this index recorded
15769    /// for its head and name; a surviving `namespace_definition` ancestor
15770    /// carries its own node range and name token, which is what the ordinary
15771    /// namespace visit records.
15772    pub fn enclosing_namespace_levels(
15773        &self,
15774        node: Node<'_>,
15775        source: &str,
15776    ) -> Vec<(String, Range, Range)> {
15777        let mut parsed = Vec::new();
15778        let mut current = node.parent();
15779        while let Some(parent) = current {
15780            if parent.kind() == "namespace_definition"
15781                && let Some(name) = parent.child_by_field_name("name")
15782                && let Some(components) = cpp_name_component_ranges(name, source)
15783            {
15784                let range = node_range(parent);
15785                parsed.push((
15786                    parent.start_byte(),
15787                    components
15788                        .into_iter()
15789                        .map(|(component, name_range)| (component, range, name_range))
15790                        .collect(),
15791                    namespace_definition_has_real_braces(parent),
15792                ));
15793            }
15794            current = parent.parent();
15795        }
15796        parsed.reverse();
15797        self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
15798            region
15799                .components
15800                .iter()
15801                .take(active_components)
15802                .cloned()
15803                .zip(
15804                    region
15805                        .component_ranges
15806                        .iter()
15807                        .take(active_components)
15808                        .copied(),
15809                )
15810                .zip(
15811                    region
15812                        .component_name_ranges
15813                        .iter()
15814                        .take(active_components)
15815                        .copied(),
15816                )
15817                .map(|((component, range), name_range)| (component, range, name_range))
15818                .collect()
15819        })
15820    }
15821
15822    /// [`Self::enclosing_namespace_components`] for a caller that has already
15823    /// climbed the ancestor chain: `parsed` lists the node's named
15824    /// `namespace_definition` ancestors outermost first, each with its start
15825    /// byte and whether its own opening and closing braces survived parsing.
15826    /// The answer follows the authority rule on [`OrphanedNamespaceScopeIndex`].
15827    pub fn restore_enclosing_namespaces(
15828        &self,
15829        parsed: Vec<(usize, Vec<String>, bool)>,
15830        node_start: usize,
15831    ) -> Vec<String> {
15832        self.splice_recovered_path(parsed, node_start, |region, active_components| {
15833            region
15834                .components
15835                .iter()
15836                .take(active_components)
15837                .cloned()
15838                .collect()
15839        })
15840    }
15841
15842    /// Splice a node's parsed namespace ancestors onto the path a recovered
15843    /// region restores, by the authority rule on [`OrphanedNamespaceScopeIndex`].
15844    /// `recovered` receives the region and the number of its outermost levels
15845    /// still open at the node, and reads those levels in whatever shape the
15846    /// caller needs.
15847    fn splice_recovered_path<T>(
15848        &self,
15849        parsed: Vec<(usize, Vec<T>, bool)>,
15850        node_start: usize,
15851        recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
15852    ) -> Vec<T> {
15853        let Some(region) = self.region_at(node_start) else {
15854            return parsed
15855                .into_iter()
15856                .flat_map(|(_, levels, _)| levels)
15857                .collect();
15858        };
15859        debug_assert_eq!(
15860            region.components.len(),
15861            region.component_closes.len(),
15862            "every recovered namespace component carries its close proof: {region:?}"
15863        );
15864        debug_assert_eq!(
15865            region.components.len(),
15866            region.component_name_ranges.len(),
15867            "every recovered namespace component carries its name token: {region:?}"
15868        );
15869        if region.components.is_empty() {
15870            return parsed
15871                .into_iter()
15872                .filter(|(_, _, complete)| *complete)
15873                .flat_map(|(_, levels, _)| levels)
15874                .collect();
15875        }
15876        let active_components = region
15877            .component_closes
15878            .iter()
15879            .take_while(|close| {
15880                close
15881                    .as_ref()
15882                    .is_none_or(|close| node_start < close.end_byte)
15883            })
15884            .count();
15885        recovered(region, active_components)
15886            .into_iter()
15887            .chain(
15888                parsed
15889                    .into_iter()
15890                    .filter(|(start, _, _)| *start >= region.start)
15891                    .flat_map(|(_, levels, _)| levels),
15892            )
15893            .collect()
15894    }
15895}
15896
15897/// Whether a parsed namespace owns real AST tokens for both of its boundary
15898/// braces. Recovery may damage its body while leaving this boundary usable as
15899/// lexical ownership evidence.
15900pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
15901    debug_assert_eq!(namespace.kind(), "namespace_definition");
15902    let Some(body) = namespace.child_by_field_name("body") else {
15903        return false;
15904    };
15905    let mut opening = false;
15906    let mut closing = false;
15907    let mut cursor = body.walk();
15908    for child in body.children(&mut cursor) {
15909        match child.kind() {
15910            "{" if !child.is_missing() => opening = true,
15911            "}" if !child.is_missing() => closing = true,
15912            _ => {}
15913        }
15914    }
15915    opening && closing
15916}
15917
15918/// The `namespace` keyword and name components of the namespace a stray `{`
15919/// opens, or `None` when the brace does not follow a namespace head.
15920///
15921/// When a namespace body holds a construct tree-sitter cannot parse, recovery
15922/// can collapse the whole `namespace Name { ... }` into one `ERROR` instead of
15923/// a `namespace_definition`: the head survives as the node's own `namespace`
15924/// keyword, name and `{` tokens, in that order, and everything the namespace
15925/// declares becomes a flat sibling of them (Catch2's `catch_decomposer.hpp`,
15926/// issue #3084). Read the head from those siblings so the brace stack names
15927/// the scope the brace opens. An anonymous namespace has no representable
15928/// name and keeps an opaque scope.
15929///
15930/// `preceding` is the open brace's preceding siblings, nearest last. The
15931/// caller already holds the container's child list; `Node::prev_sibling`
15932/// would re-descend from the root for each step (#3141).
15933fn recovered_namespace_open_components<'tree>(
15934    preceding: &[Node<'tree>],
15935    source: &str,
15936) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15937    let mut head = Vec::new();
15938    for &sibling in preceding.iter().rev() {
15939        if sibling.kind() != "comment" {
15940            head.push(sibling);
15941            if head.len() == 2 {
15942                break;
15943            }
15944        }
15945    }
15946    let [name, keyword] = head[..] else {
15947        return None;
15948    };
15949    if keyword.kind() != "namespace" {
15950        return None;
15951    }
15952    let components = cpp_name_component_ranges(name, source)?;
15953    (!components.is_empty()).then_some((keyword, components))
15954}
15955
15956/// The written head and name-token ranges of the namespace opened by `body`.
15957/// Ordinary namespace bodies and the two damaged literal heads share the
15958/// existing brace-stack lifetime; a recovered compound body's end is NOT a
15959/// namespace close proof (#5868).
15960fn namespace_body_name_components<'tree>(
15961    parent: Node<'tree>,
15962    body: Node<'tree>,
15963    source: &str,
15964) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15965    if parent.child_by_field_name("body") != Some(body) {
15966        return None;
15967    }
15968    let (written_at, name) = match (parent.kind(), body.kind()) {
15969        ("namespace_definition", "declaration_list") => {
15970            (parent, parent.child_by_field_name("name")?)
15971        }
15972        ("function_definition", "compound_statement") => {
15973            // The two wrapper shapes are mutually exclusive on child kinds: a
15974            // recovered head has an ERROR where the clean macro-modifier head
15975            // has the name. Recovery inside the body still flags has_error on
15976            // the clean shape, so the shape checks, not has_error, must pick
15977            // the reader.
15978            recovered_literal_namespace_head(parent, body, source)
15979                .or_else(|| macro_modifier_namespace_head(parent, body, source))?
15980        }
15981        _ => return None,
15982    };
15983    let components = cpp_name_component_ranges(name, source)?;
15984    (!components.is_empty()).then_some((written_at, components))
15985}
15986
15987/// The written head and name of the literal namespace a damaged wrapper opens,
15988/// when recovery read the `namespace` keyword as a function declarator.
15989fn recovered_literal_namespace_head<'tree>(
15990    parent: Node<'tree>,
15991    body: Node<'tree>,
15992    source: &str,
15993) -> Option<(Node<'tree>, Node<'tree>)> {
15994    // An opaque prefix can make the parser read the literal namespace
15995    // keyword as a function declarator. Account for every direct token:
15996    // no parameters, attributes, extra names or punctuation may compete.
15997    let mut cursor = parent.walk();
15998    let children = parent
15999        .children(&mut cursor)
16000        .filter(|child| child.kind() != "comment")
16001        .collect::<Vec<_>>();
16002    let [prefix, keyword, error, actual_body] = children.as_slice() else {
16003        return None;
16004    };
16005    if *actual_body != body
16006        || parent.child_by_field_name("type") != Some(*prefix)
16007        || parent.child_by_field_name("declarator") != Some(*keyword)
16008        || prefix.kind() != "type_identifier"
16009        || prefix.is_missing()
16010        || prefix.has_error()
16011        || prefix.child_count() != 0
16012        || keyword.kind() != "identifier"
16013        || keyword.is_missing()
16014        || keyword.has_error()
16015        || keyword.child_count() != 0
16016        || node_text(*keyword, source) != "namespace"
16017        || error.kind() != "ERROR"
16018        || error.is_missing()
16019        || body.is_missing()
16020    {
16021        return None;
16022    }
16023    let mut cursor = error.walk();
16024    let names = error
16025        .children(&mut cursor)
16026        .filter(|child| child.kind() != "comment")
16027        .collect::<Vec<_>>();
16028    let [name] = names.as_slice() else {
16029        return None;
16030    };
16031    if !matches!(name.kind(), "identifier" | "namespace_identifier")
16032        || name.is_missing()
16033        || name.has_error()
16034        || name.child_count() != 0
16035    {
16036        return None;
16037    }
16038    let open = body.child(0)?;
16039    if open.kind() != "{"
16040        || open.is_missing()
16041        || open.has_error()
16042        || open.start_byte() != body.start_byte()
16043    {
16044        return None;
16045    }
16046    // Keep the literal keyword's provenance, never the unrelated prefix
16047    // or the prematurely closed function envelope (#3309, #4292).
16048    Some((*keyword, *name))
16049}
16050
16051/// The written head and name of the literal namespace a macro-modifier
16052/// wrapper opens, when the macro-declaration grammar read the same construct
16053/// without recovery.
16054fn macro_modifier_namespace_head<'tree>(
16055    parent: Node<'tree>,
16056    body: Node<'tree>,
16057    source: &str,
16058) -> Option<(Node<'tree>, Node<'tree>)> {
16059    // The macro-declaration grammar reads the same construct without
16060    // recovery: the opaque prefix is a `macro_modifier`, the literal
16061    // `namespace` keyword occupies the type field, and the namespace
16062    // name is the declarator. The keyword cannot be a type, so its
16063    // presence in the type field is the recovery evidence the older
16064    // grammar's `ERROR` carried; account for every direct token the
16065    // same way.
16066    let mut cursor = parent.walk();
16067    let children = parent
16068        .children(&mut cursor)
16069        .filter(|child| child.kind() != "comment")
16070        .collect::<Vec<_>>();
16071    let [prefix, keyword, name, actual_body] = children.as_slice() else {
16072        return None;
16073    };
16074    if *actual_body != body
16075        || parent.child_by_field_name("type") != Some(*keyword)
16076        || parent.child_by_field_name("declarator") != Some(*name)
16077        || prefix.kind() != "macro_modifier"
16078        || prefix.is_missing()
16079        || prefix.has_error()
16080        || macro_modifier_identifier(*prefix).is_none()
16081        || keyword.kind() != "type_identifier"
16082        || keyword.is_missing()
16083        || keyword.has_error()
16084        || keyword.child_count() != 0
16085        || node_text(*keyword, source) != "namespace"
16086        || !matches!(name.kind(), "identifier" | "namespace_identifier")
16087        || name.is_missing()
16088        || name.has_error()
16089        || name.child_count() != 0
16090        || body.is_missing()
16091    {
16092        return None;
16093    }
16094    let open = body.child(0)?;
16095    if open.kind() != "{"
16096        || open.is_missing()
16097        || open.has_error()
16098        || open.start_byte() != body.start_byte()
16099    {
16100        return None;
16101    }
16102    // Keep the literal keyword's provenance, never the unrelated prefix
16103    // or the prematurely closed function envelope (#3309, #4292).
16104    Some((*keyword, *name))
16105}
16106
16107#[derive(Clone, Copy)]
16108enum NamespaceBodyBraceProof {
16109    Real(Range),
16110    Missing,
16111}
16112
16113/// Prove a namespace body's boundary from its direct AST children. Require the
16114/// body's boundary as well as direct ownership, so a stray brace inside a
16115/// recovered body cannot become namespace ownership evidence. Unknown is
16116/// distinct from a proven missing token: unknown leaves the global pairing in
16117/// place, while a direct MISSING `}` removes a misleading pairing.
16118fn namespace_body_brace_proof(body: Node<'_>) -> Option<NamespaceBodyBraceProof> {
16119    if body.kind() != "declaration_list" {
16120        return None;
16121    }
16122    let mut cursor = body.walk();
16123    let children = body.children(&mut cursor).collect::<Vec<_>>();
16124    let close_index = children
16125        .iter()
16126        .rposition(|child| child.kind() == "}" && child.end_byte() == body.end_byte())?;
16127    let close = children[close_index];
16128    if close.is_missing() {
16129        return Some(NamespaceBodyBraceProof::Missing);
16130    }
16131    let namespace_terminator_sibling = body
16132        .parent()
16133        .filter(|namespace| namespace.kind() == "namespace_definition")
16134        .and_then(|namespace| namespace.next_sibling())
16135        .is_some_and(|sibling| {
16136            sibling.kind() == ";"
16137                || (sibling.kind() == "expression_statement"
16138                    && sibling.child_count() == 1
16139                    && sibling.child(0).is_some_and(|child| child.kind() == ";"))
16140        });
16141    // A recovered class close can become the namespace body boundary while
16142    // its class terminator remains as a sibling semicolon. A malformed class
16143    // can also leak its final expression or label directly into the namespace
16144    // body. Neither shape proves that this `}` belongs to the namespace.
16145    if namespace_terminator_sibling
16146        || children
16147            .get(close_index + 1)
16148            .is_some_and(|sibling| sibling.kind() == ";")
16149        || children[..close_index]
16150            .iter()
16151            .rev()
16152            .find(|child| !child.is_extra() && child.kind() != "{")
16153            .is_some_and(|previous| {
16154                matches!(
16155                    previous.kind(),
16156                    "expression_statement" | "labeled_statement"
16157                )
16158            })
16159    {
16160        return None;
16161    }
16162    Some(NamespaceBodyBraceProof::Real(node_range(close)))
16163}
16164
16165#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16166pub enum RecoveredDeclaratorTypeContext {
16167    Declaration,
16168    FunctionDefinition,
16169    Parameter,
16170}
16171
16172/// Recognize a real type displaced into a qualified declarator by parser
16173/// recovery.
16174///
16175/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
16176/// type and `Result` were the scope of a qualified declarator with a missing
16177/// `::`. A template return such as `API Result<T> make()` uses a
16178/// `template_type` for the same recovered scope. The same recovery occurs for
16179/// macro-prefixed definitions, extern variables, and macro-decorated
16180/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
16181/// the macro). Keep this intentionally structural: the recovered scope must
16182/// have the grammar's missing separator, the qualified node must occupy the
16183/// declaration's declarator chain, a separate nonempty type must occupy the
16184/// normal type field, and the recovered name must unwrap to a real declarator
16185/// name.
16186pub fn recovered_macro_decorated_declarator_type(
16187    node: Node<'_>,
16188) -> Option<RecoveredDeclaratorTypeContext> {
16189    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
16190}
16191
16192/// Return the declaration/function `type` displaced by a macro-shaped
16193/// qualified declarator, together with the enclosing declaration context.
16194/// Callers use the macro scope only as structural admission evidence; the
16195/// returned node is the real type reference to resolve and record.
16196pub fn recovered_macro_decorated_type_node(
16197    node: Node<'_>,
16198) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
16199    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
16200        return None;
16201    }
16202    let qualified = node.parent()?;
16203    if qualified.kind() != "qualified_identifier"
16204        || qualified.child_by_field_name("scope") != Some(node)
16205        || !(0..qualified.child_count())
16206            .filter_map(|index| qualified.child(index))
16207            .any(|child| child.kind() == "::" && child.is_missing())
16208    {
16209        return None;
16210    }
16211    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
16212        return None;
16213    }
16214
16215    let (declaration, context) = recovered_declarator_container(qualified)?;
16216    let type_node = declaration
16217        .child_by_field_name("type")
16218        .filter(|type_node| {
16219            *type_node != qualified
16220                && !type_node.is_missing()
16221                && type_node.start_byte() != type_node.end_byte()
16222        })?;
16223    Some((type_node, context))
16224}
16225
16226/// The macro-declaration grammar's clean reading of the head
16227/// [`recovered_macro_decorated_type_node`] recovers: `API static inline T
16228/// make()` parses as a leading `macro_modifier` and `T` as the declaration's
16229/// `type`. Accepts the same type kinds and declaration containers as the
16230/// recovered form, so a rule keyed on that macro-decorated head keeps its
16231/// scope across both grammars.
16232pub fn macro_modifier_decorated_type_node(node: Node<'_>) -> bool {
16233    matches!(node.kind(), "type_identifier" | "template_type")
16234        && node.parent().is_some_and(|declaration| {
16235            matches!(
16236                declaration.kind(),
16237                "declaration"
16238                    | "function_definition"
16239                    | "parameter_declaration"
16240                    | "optional_parameter_declaration"
16241            ) && declaration.child_by_field_name("type") == Some(node)
16242                && declaration.child_by_field_name("declarator").is_some()
16243                && crate::declarations::cpp_declaration_type_follows_macro_modifier(
16244                    declaration,
16245                    node,
16246                )
16247        })
16248}
16249
16250fn recovered_declarator_container(
16251    mut declarator: Node<'_>,
16252) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
16253    loop {
16254        let parent = declarator.parent()?;
16255        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
16256            return Some((
16257                parent
16258                    .parent()
16259                    .filter(|declaration| declaration.kind() == "declaration")?,
16260                RecoveredDeclaratorTypeContext::Declaration,
16261            ));
16262        }
16263        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
16264            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
16265        }
16266        if parent.kind() == "function_definition"
16267            && has_field_child(parent, "declarator", declarator)
16268        {
16269            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
16270        }
16271        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
16272        // level down: the parameter's `type` field takes the macro token and
16273        // the real type `T` becomes the recovered scope of the declarator.
16274        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
16275        // candidate at all (#1830).
16276        if matches!(
16277            parent.kind(),
16278            "parameter_declaration" | "optional_parameter_declaration"
16279        ) && has_field_child(parent, "declarator", declarator)
16280        {
16281            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
16282        }
16283        if !matches!(
16284            parent.kind(),
16285            "array_declarator"
16286                | "function_declarator"
16287                | "parenthesized_declarator"
16288                | "pointer_declarator"
16289                | "pointer_type_declarator"
16290                | "reference_declarator"
16291        ) || !has_field_child(parent, "declarator", declarator)
16292        {
16293            return None;
16294        }
16295        declarator = parent;
16296    }
16297}
16298
16299fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
16300    brokk_bifrost_core::analyzer::structural::adapter_helpers::is_field_of(parent, target, field)
16301}
16302
16303fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
16304    loop {
16305        if node.is_missing() || node.start_byte() == node.end_byte() {
16306            return false;
16307        }
16308        match node.kind() {
16309            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
16310                return true;
16311            }
16312            "array_declarator"
16313            | "function_declarator"
16314            | "parenthesized_declarator"
16315            | "pointer_declarator"
16316            | "pointer_type_declarator"
16317            | "reference_declarator" => {
16318                let Some(declarator) = node.child_by_field_name("declarator") else {
16319                    return false;
16320                };
16321                node = declarator;
16322            }
16323            _ => return false,
16324        }
16325    }
16326}
16327
16328/// Aggregate-owner proof for a structurally recognized designated initializer.
16329pub enum DesignatedInitializerOwner {
16330    Resolved(CodeUnit),
16331    Unresolved,
16332}
16333
16334enum InitializerOwnerStep {
16335    Field(String),
16336    AggregateWrapper,
16337}
16338
16339/// Recognize a designated-initializer field and, when possible, resolve its
16340/// aggregate owner.
16341///
16342/// Covers both the grammar's ordinary `field_designator` shape and the exact
16343/// recovery used for `.field = value` after a preprocessor-split array
16344/// initializer. Ordered multi-component designators follow each preceding
16345/// field's declared aggregate type. `None` means the node is not a designator
16346/// at all; an unresolved designator remains classified so callers cannot fall
16347/// through to unrelated global/member heuristics.
16348pub fn designated_initializer_owner(
16349    analyzer: &CppGraphSource<'_>,
16350    visibility: &VisibilityIndex<'_>,
16351    file: &ProjectFile,
16352    source: &str,
16353    node: Node<'_>,
16354) -> Option<DesignatedInitializerOwner> {
16355    if let Some(designator) = node
16356        .parent()
16357        .filter(|parent| parent.kind() == "field_designator")
16358    {
16359        let pair = designator.parent()?;
16360        if pair.kind() != "initializer_pair" {
16361            return None;
16362        }
16363        let mut cursor = pair.walk();
16364        let designators = pair
16365            .children_by_field_name("designator", &mut cursor)
16366            .collect::<Vec<_>>();
16367        let position = designators
16368            .iter()
16369            .position(|candidate| same_node(*candidate, designator))?;
16370        let initializer = pair.parent()?;
16371        if initializer.kind() != "initializer_list" {
16372            return None;
16373        }
16374        let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
16375        for prior in &designators[..position] {
16376            let field = prior
16377                .child_by_field_name("field")
16378                .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
16379            owner = owner.and_then(|owner| {
16380                initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
16381            });
16382        }
16383        return Some(classified_designated_owner(owner));
16384    }
16385
16386    let init_declarator = node.parent()?;
16387    if init_declarator.child_by_field_name("declarator") != Some(node)
16388        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
16389    {
16390        return None;
16391    }
16392    Some(classified_designated_owner(declaration_owner(
16393        analyzer,
16394        visibility,
16395        file,
16396        source,
16397        init_declarator.parent()?,
16398    )))
16399}
16400
16401fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
16402    owner.map_or(
16403        DesignatedInitializerOwner::Unresolved,
16404        DesignatedInitializerOwner::Resolved,
16405    )
16406}
16407
16408fn initializer_list_owner(
16409    analyzer: &CppGraphSource<'_>,
16410    visibility: &VisibilityIndex<'_>,
16411    file: &ProjectFile,
16412    source: &str,
16413    initializer: Node<'_>,
16414) -> Option<CodeUnit> {
16415    let mut current = initializer;
16416    let mut steps = Vec::new();
16417    loop {
16418        let parent = current.parent()?;
16419        match parent.kind() {
16420            "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
16421                let designator = parent.child_by_field_name("designator")?;
16422                let step = designator
16423                    .child_by_field_name("field")
16424                    .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
16425                    .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
16426                    .unwrap_or(InitializerOwnerStep::AggregateWrapper);
16427                steps.push(step);
16428                current = parent.parent()?;
16429            }
16430            "initializer_list" => {
16431                current = parent;
16432            }
16433            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
16434                let declaration = parent.parent()?;
16435                let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
16436                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
16437            }
16438            "compound_literal_expression"
16439                if parent.child_by_field_name("value") == Some(current) =>
16440            {
16441                let type_node = parent.child_by_field_name("type")?;
16442                let owner =
16443                    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
16444                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
16445            }
16446            "ERROR" => current = parent,
16447            _ => return None,
16448        }
16449    }
16450}
16451
16452fn apply_initializer_owner_steps(
16453    analyzer: &CppGraphSource<'_>,
16454    visibility: &VisibilityIndex<'_>,
16455    file: &ProjectFile,
16456    mut owner: CodeUnit,
16457    steps: Vec<InitializerOwnerStep>,
16458) -> Option<CodeUnit> {
16459    for step in steps.into_iter().rev() {
16460        if let InitializerOwnerStep::Field(field_name) = step {
16461            owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
16462        }
16463    }
16464    Some(owner)
16465}
16466
16467fn initializer_field_owner(
16468    analyzer: &CppGraphSource<'_>,
16469    visibility: &VisibilityIndex<'_>,
16470    file: &ProjectFile,
16471    owner: CodeUnit,
16472    field_name: &str,
16473) -> Option<CodeUnit> {
16474    let fields = visibility
16475        .visible_members_for_owner_name(file, &owner, field_name)
16476        .into_iter()
16477        .filter(|field| field.is_field())
16478        .collect::<Vec<_>>();
16479    let field = match fields.as_slice() {
16480        [field] => *field,
16481        _ => return None,
16482    };
16483    field_declared_binding(analyzer, visibility, file, field)?.unit
16484}
16485
16486fn declaration_owner(
16487    analyzer: &CppGraphSource<'_>,
16488    visibility: &VisibilityIndex<'_>,
16489    file: &ProjectFile,
16490    source: &str,
16491    declaration: Node<'_>,
16492) -> Option<CodeUnit> {
16493    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
16494        return None;
16495    }
16496    let type_node = declaration
16497        .child_by_field_name("type")
16498        .or_else(|| first_type_child(declaration))?;
16499    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
16500}
16501
16502fn resolve_designated_owner_type(
16503    analyzer: &CppGraphSource<'_>,
16504    visibility: &VisibilityIndex<'_>,
16505    file: &ProjectFile,
16506    source: &str,
16507    type_node: Node<'_>,
16508) -> Option<CodeUnit> {
16509    if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
16510        return Some(owner);
16511    }
16512    if !is_globally_qualified_cpp_name(type_node)
16513        && let Some(components) = cpp_type_name_components(type_node, source)
16514        && let Some(resolution) =
16515            function_local_type_resolution(analyzer, file, type_node, &components, source)
16516    {
16517        return match resolution {
16518            LexicalTypeResolution::Resolved { unit, .. } => Some(unit).filter(CodeUnit::is_class),
16519            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
16520        };
16521    }
16522    let type_name = normalize_type_text(node_text(type_node, source));
16523    visibility
16524        .resolve_type(file, &type_name)
16525        .filter(CodeUnit::is_class)
16526}
16527
16528pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
16529    let mut cursor = node.walk();
16530    node.named_children(&mut cursor).find(|child| {
16531        matches!(
16532            child.kind(),
16533            "type_identifier"
16534                | "primitive_type"
16535                | "qualified_identifier"
16536                | "scoped_type_identifier"
16537                | "struct_specifier"
16538                | "union_specifier"
16539                | "enum_specifier"
16540        )
16541    })
16542}
16543
16544pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
16545    visibility: &VisibilityIndex<'_>,
16546    file: &ProjectFile,
16547    source: &str,
16548    declarator: Node<'_>,
16549    type_text: Option<&str>,
16550    bindings: &LocalInferenceEngine<T>,
16551) -> bool {
16552    if !has_ancestor_kind(declarator, "compound_statement") {
16553        return false;
16554    }
16555    if declarator
16556        .child_by_field_name("declarator")
16557        .is_none_or(|declarator| declarator.kind() != "identifier")
16558    {
16559        return false;
16560    }
16561    if !type_text
16562        .and_then(|text| visibility.resolve_type(file, text))
16563        .is_some_and(|unit| unit.is_class())
16564    {
16565        return false;
16566    }
16567    declarator
16568        .child_by_field_name("parameters")
16569        .is_some_and(|parameters| {
16570            constructor_parameters_look_like_expressions(parameters, source, bindings)
16571        })
16572}
16573
16574fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
16575    parameters: Node<'_>,
16576    source: &str,
16577    bindings: &LocalInferenceEngine<T>,
16578) -> bool {
16579    let mut cursor = parameters.walk();
16580    parameters.named_children(&mut cursor).any(|parameter| {
16581        !matches!(
16582            parameter.kind(),
16583            "parameter_declaration" | "optional_parameter_declaration"
16584        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
16585    })
16586}
16587
16588fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
16589    parameter: Node<'_>,
16590    source: &str,
16591    bindings: &LocalInferenceEngine<T>,
16592) -> bool {
16593    let text = node_text(parameter, source).trim();
16594    if text
16595        .chars()
16596        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
16597        && bindings.is_shadowed(text)
16598    {
16599        return true;
16600    }
16601
16602    let Some(base) = parameter
16603        .child_by_field_name("type")
16604        .filter(|base| base.kind() == "type_identifier")
16605    else {
16606        return false;
16607    };
16608    let Some(subscript) = parameter
16609        .child_by_field_name("declarator")
16610        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
16611    else {
16612        return false;
16613    };
16614    subscript.child_by_field_name("size").is_some()
16615        && bindings.is_shadowed(node_text(base, source).trim())
16616}
16617
16618pub fn is_declaration_name(node: Node<'_>) -> bool {
16619    let Some(parent) = node.parent() else {
16620        return false;
16621    };
16622    if parent
16623        .child_by_field_name("name")
16624        .is_some_and(|name| same_node(name, node))
16625    {
16626        if matches!(
16627            parent.kind(),
16628            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
16629        ) {
16630            return cpp_tag_specifier_declares_name(parent);
16631        }
16632        if matches!(
16633            parent.kind(),
16634            "namespace_definition"
16635                | "namespace_alias_definition"
16636                | "alias_declaration"
16637                | "enumerator"
16638        ) {
16639            return true;
16640        }
16641    }
16642
16643    let mut current = Some(parent);
16644    while let Some(ancestor) = current {
16645        let type_definition = ancestor.kind() == "type_definition";
16646        let mut child_cursor = ancestor.walk();
16647        if ancestor.named_children(&mut child_cursor).any(|child| {
16648            declaration_declarator(ancestor, child).is_some_and(|declarator| {
16649                declarator_name_path_contains(declarator, node, type_definition)
16650            })
16651        }) {
16652            return true;
16653        }
16654        if matches!(
16655            ancestor.kind(),
16656            "declaration"
16657                | "field_declaration"
16658                | "parameter_declaration"
16659                | "optional_parameter_declaration"
16660                | "function_definition"
16661                | "type_definition"
16662                | "alias_declaration"
16663                | "template_instantiation"
16664                | "class_specifier"
16665                | "struct_specifier"
16666                | "union_specifier"
16667                | "enum_specifier"
16668        ) {
16669            return false;
16670        }
16671        current = ancestor.parent();
16672    }
16673    false
16674}
16675
16676/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
16677/// declaration's declarator inside a malformed class body.
16678///
16679/// An export macro between `class` and the class name can make the containing
16680/// body parse as a function body. A source declaration such as
16681/// `friend class internal::Friend;` then retains this exact structure:
16682/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
16683/// The declarator is a type reference despite its field role.
16684pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
16685    if !matches!(
16686        node.kind(),
16687        "qualified_identifier" | "scoped_type_identifier"
16688    ) {
16689        return false;
16690    }
16691    let Some(declaration) = node
16692        .parent()
16693        .filter(|parent| parent.kind() == "declaration")
16694    else {
16695        return false;
16696    };
16697    if declaration.child_by_field_name("declarator") != Some(node)
16698        || !declaration
16699            .child_by_field_name("type")
16700            .is_some_and(|friend| {
16701                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
16702            })
16703    {
16704        return false;
16705    }
16706    let mut cursor = declaration.walk();
16707    let mut errors = declaration
16708        .named_children(&mut cursor)
16709        .filter(|child| child.kind() == "ERROR");
16710    let Some(error) = errors.next() else {
16711        return false;
16712    };
16713    errors.next().is_none()
16714        && error.named_child_count() == 1
16715        && error.named_child(0).is_some_and(|class| {
16716            class.kind() == "identifier" && node_text(class, source) == "class"
16717        })
16718}
16719
16720pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
16721    if !matches!(node.kind(), "identifier" | "field_identifier") {
16722        return false;
16723    }
16724    if let Some(parent) = node.parent() {
16725        if parent.kind() == "call_expression"
16726            && parent.child_by_field_name("function") == Some(node)
16727        {
16728            return false;
16729        }
16730        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
16731            && parent.child_by_field_name("label") == Some(node)
16732        {
16733            return false;
16734        }
16735    }
16736    if is_declaration_name(node) {
16737        return false;
16738    }
16739    let mut current = node.parent();
16740    while let Some(ancestor) = current {
16741        match ancestor.kind() {
16742            "preproc_ifdef" | "preproc_ifndef" => {
16743                if ancestor
16744                    .child_by_field_name("name")
16745                    .is_some_and(|name| node_range_contains(name, node))
16746                {
16747                    return false;
16748                }
16749            }
16750            "preproc_if" | "preproc_elif" => {
16751                if ancestor
16752                    .child_by_field_name("condition")
16753                    .is_some_and(|condition| node_range_contains(condition, node))
16754                {
16755                    return false;
16756                }
16757            }
16758            "preproc_else" => {}
16759            kind if kind.starts_with("preproc_") => return false,
16760            _ => {}
16761        }
16762        if matches!(
16763            ancestor.kind(),
16764            "translation_unit" | "function_definition" | "compound_statement"
16765        ) {
16766            break;
16767        }
16768        current = ancestor.parent();
16769    }
16770    true
16771}
16772
16773fn recovered_c_reference_node(
16774    visibility: &VisibilityIndex<'_>,
16775    file: &ProjectFile,
16776    node: Node<'_>,
16777    source: &str,
16778) -> bool {
16779    if node.start_byte() >= node.end_byte()
16780        || node.is_error()
16781        || node.is_missing()
16782        || !matches!(
16783            node.kind(),
16784            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
16785        )
16786        || recovered_c_macro_binding_role(node)
16787        || recovered_c_label_role(node)
16788    {
16789        return false;
16790    }
16791    // A declaration name can be an identifier child of a recovered ERROR
16792    // (for example `EFI_STATUS Encode()` in C files parsed as C++). Do not
16793    // let a same-named macro turn that binder into a reference. An
16794    // assignment whose C callee follows the recovered `explicit` token is
16795    // the one expression-shaped exception: the generic declarator walk sees
16796    // its function_declarator as a declaration path, but the ERROR sibling
16797    // proves it is a call.
16798    let name = node_text(node, source);
16799    let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
16800    let recovered_macro_call = recovered_c_function_declarator_invocation(node)
16801        && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
16802    let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
16803    if is_declaration_name(node)
16804        && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
16805        && !recovered_function_call
16806        && !recovered_macro_call
16807        && !recovered_parenthesized_reference
16808    {
16809        return false;
16810    }
16811
16812    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
16813        return true;
16814    }
16815    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
16816        return true;
16817    }
16818    if recovered_parenthesized_reference {
16819        return true;
16820    }
16821    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
16822        if recovered_function_call {
16823            return true;
16824        }
16825        return visibility
16826            .visible_identifier_candidates(file, name)
16827            .any(|candidate| candidate.is_class() || candidate.is_module());
16828    }
16829    let visible = visibility
16830        .visible_identifier_candidates(file, name)
16831        .next()
16832        .is_some();
16833    visible
16834        && (recovered_c_reference_anchor(node)
16835            || recovered_c_error_expression_leaf(node)
16836            || recovered_function_call)
16837}
16838
16839fn push_recovered_c_range(
16840    ranges: &mut Vec<Range>,
16841    seen: &mut HashSet<(usize, usize)>,
16842    start_byte: usize,
16843    end_byte: usize,
16844    node: Node<'_>,
16845    limit: usize,
16846) -> bool {
16847    if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
16848        return true;
16849    }
16850    if ranges.len() >= limit {
16851        return false;
16852    }
16853    ranges.push(Range {
16854        start_byte,
16855        end_byte,
16856        start_line: node.start_position().row,
16857        end_line: node.end_position().row,
16858    });
16859    true
16860}
16861
16862/// A reference leaf can sit directly beneath an ERROR while its ERROR parent
16863/// is still attached to a real expression (most often a recovered macro call
16864/// argument). The expression parent is the structured proof; an unindexed
16865/// identifier beneath a bare recovery envelope has no such proof.
16866fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
16867    let mut current = node.parent();
16868    while let Some(parent) = current {
16869        if parent.is_error() {
16870            let Some(anchor) = parent.parent() else {
16871                return false;
16872            };
16873            return anchor.kind().ends_with("_expression")
16874                || matches!(
16875                    anchor.kind(),
16876                    "argument_list"
16877                        | "return_statement"
16878                        | "expression_statement"
16879                        | "case_statement"
16880                        | "initializer_list"
16881                        | "field_designator"
16882                        | "enumerator"
16883                );
16884        }
16885        if matches!(
16886            parent.kind(),
16887            "translation_unit" | "function_definition" | "compound_statement"
16888        ) {
16889            return false;
16890        }
16891        current = parent.parent();
16892    }
16893    false
16894}
16895
16896/// C recovery may represent a call as `identifier > function_declarator >
16897/// function_declarator > ERROR > compound_statement`. This shape is only a
16898/// call when the malformed declarator is attached to a real function body and
16899/// the name is an indexed visible callable. A declaration's
16900/// `ERROR > function_declarator > declaration` shape deliberately fails this
16901/// test.
16902fn recovered_c_function_call(
16903    visibility: &VisibilityIndex<'_>,
16904    file: &ProjectFile,
16905    node: Node<'_>,
16906    name: &str,
16907) -> bool {
16908    if !matches!(
16909        node.kind(),
16910        "identifier" | "field_identifier" | "type_identifier"
16911    ) {
16912        return false;
16913    }
16914    // C++ keywords used as C arguments can leave only the call prefix under
16915    // ERROR, with the remaining arguments in a following expression statement.
16916    // Require a statement boundary in a real block, an opening parenthesis,
16917    // and a recovered argument. A bare ERROR identifier supplies no call role.
16918    let error_call_prefix = node.parent().is_some_and(|error| {
16919        error.is_error()
16920            && error
16921                .parent()
16922                .is_some_and(|parent| parent.kind() == "compound_statement")
16923    }) && node
16924        .prev_sibling()
16925        .is_none_or(|previous| previous.kind() == ";")
16926        && node.next_sibling().is_some_and(|open| {
16927            open.kind() == "("
16928                && open.next_named_sibling().is_some_and(|argument| {
16929                    argument.kind() == "parameter_declaration" && !argument.has_error()
16930                })
16931        });
16932    (error_call_prefix || recovered_c_function_declarator_invocation(node))
16933        && visibility
16934            .visible_identifier_candidates(file, name)
16935            .any(CodeUnit::is_function)
16936}
16937
16938/// The bogus `function_definition` the macro-declaration grammar builds for a
16939/// C call whose argument list a Clang block literal breaks: `f(first, ^(T b)
16940/// { ... })` reads as a definition whose declarator ends at the block's `(`,
16941/// with the block body as the function body. The older grammar kept the
16942/// `call_expression` and left the damage inside its argument list. The
16943/// declarator must carry the damage, and the envelope must sit in statement
16944/// context: a clean type-less definition is a real one (a constructor).
16945pub fn recovered_c_block_call_envelope(declarator: Node<'_>) -> Option<Node<'_>> {
16946    if declarator.kind() != "function_declarator" || !declarator.has_error() {
16947        return None;
16948    }
16949    let definition = declarator.parent().filter(|parent| {
16950        parent.kind() == "function_definition"
16951            && parent.child_by_field_name("declarator") == Some(declarator)
16952    })?;
16953    if definition.child_by_field_name("type").is_some()
16954        || definition.named_child(0) != Some(declarator)
16955        || definition.child_by_field_name("body").is_none()
16956        || definition.parent()?.kind() != "compound_statement"
16957    {
16958        return None;
16959    }
16960    Some(definition)
16961}
16962
16963/// Return whether a C identifier belongs to a call-shaped declarator that the
16964/// C++ grammar put under an `ERROR` node.
16965///
16966/// The malformed call can be direct (`f(arg)`) or nested in a parameter
16967/// declaration when one of its arguments looks like a type (`f(TYPE, value)`).
16968/// In both cases the CST retains the function-declarator and its enclosing
16969/// recovery envelope. We walk only those declarator/parameter nodes and stop
16970/// at a real expression-bearing boundary; declarations therefore cannot pass
16971/// this predicate merely because they have a parameter list.
16972fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
16973    // The macro-declaration grammar keeps a function-like macro invocation at
16974    // declaration scope as a `macro_type_specifier` (a function-like macro
16975    // wrapping a type), admitted only where the parse would otherwise contain
16976    // an error. Its name is the invocation.
16977    let mut current = if node.parent().is_some_and(|parent| {
16978        parent.kind() == "macro_type_specifier" && parent.child_by_field_name("name") == Some(node)
16979    }) {
16980        node.parent().expect("checked macro_type_specifier parent")
16981    } else {
16982        let mut function_declarator = if node.parent().is_some_and(|parent| {
16983            parent.kind() == "function_declarator"
16984                && parent.child_by_field_name("declarator") == Some(node)
16985        }) {
16986            node.parent().expect("checked function declarator parent")
16987        } else {
16988            let Some(parameter) = node.parent().filter(|parent| {
16989                parent.kind() == "parameter_declaration"
16990                    && parent.child_by_field_name("type") == Some(node)
16991            }) else {
16992                return false;
16993            };
16994            if !parameter
16995                .child_by_field_name("declarator")
16996                .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
16997            {
16998                return false;
16999            }
17000            let Some(parameters) = parameter
17001                .parent()
17002                .filter(|parent| parent.kind() == "parameter_list")
17003            else {
17004                return false;
17005            };
17006            let Some(function_declarator) = parameters
17007                .parent()
17008                .filter(|parent| parent.kind() == "function_declarator")
17009            else {
17010                return false;
17011            };
17012            function_declarator
17013        };
17014
17015        // Recovery may absorb the next statement's parenthesized cast into a
17016        // second function declarator. Follow only the original declarator chain.
17017        while let Some(parent) = function_declarator.parent().filter(|parent| {
17018            parent.kind() == "function_declarator"
17019                && parent.child_by_field_name("declarator") == Some(function_declarator)
17020        }) {
17021            function_declarator = parent;
17022        }
17023        // The macro-declaration grammar can attach the malformed call's
17024        // declarator directly to the bogus `function_definition` (a block
17025        // literal argument reads as the body) without the older grammar's
17026        // ERROR wrapper.
17027        let Some(current) = function_declarator.parent().filter(|parent| {
17028            parent.is_error() || recovered_c_block_call_envelope(function_declarator).is_some()
17029        }) else {
17030            return false;
17031        };
17032        current
17033    };
17034    loop {
17035        let Some(parent) = current.parent() else {
17036            return false;
17037        };
17038        if matches!(
17039            parent.kind(),
17040            "translation_unit"
17041                | "compound_statement"
17042                | "preproc_if"
17043                | "preproc_ifdef"
17044                | "preproc_ifndef"
17045                | "preproc_else"
17046                | "preproc_elif"
17047        ) {
17048            return true;
17049        }
17050        if parent.kind() == "function_definition"
17051            && parent.child_by_field_name("declarator") == Some(current)
17052            && parent.named_child(0) == Some(current)
17053            && parent.child_by_field_name("body").is_some()
17054        {
17055            return true;
17056        }
17057        if parent.is_error()
17058            || matches!(
17059                parent.kind(),
17060                "parameter_declaration"
17061                    | "parameter_list"
17062                    | "function_declarator"
17063                    | "abstract_function_declarator"
17064                    | "parenthesized_declarator"
17065            )
17066        {
17067            current = parent;
17068            continue;
17069        }
17070        return false;
17071    }
17072}
17073
17074/// C permits an identifier named `typename`. The C++ grammar can recover an
17075/// assignment using that identifier as a declaration whose declarator is a
17076/// parenthesized argument list, for example `typename = f(ctx, value)`. Only
17077/// the argument retained beneath the nested `ERROR` is a reference; sibling
17078/// declarator identifiers remain binders/grammar artifacts.
17079fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
17080    // The macro-declaration grammar keeps the same line as a `dependent_type`
17081    // whose `macro_type_specifier` argument list retains the first argument
17082    // beneath the nested `ERROR`; that argument's `type_descriptor` is the
17083    // reference, and the specifier's own `type` field is the following
17084    // sibling, never a reference.
17085    if node
17086        .parent()
17087        .is_some_and(|parent| parent.kind() == "type_descriptor")
17088    {
17089        let Some(type_descriptor) = node.parent() else {
17090            return false;
17091        };
17092        let Some(error) = type_descriptor.parent().filter(|parent| parent.is_error()) else {
17093            return false;
17094        };
17095        if error.named_child_count() != 1 || error.named_child(0) != Some(type_descriptor) {
17096            return false;
17097        }
17098        let Some(dependent) = error
17099            .parent()
17100            .and_then(|parent| parent.parent())
17101            .filter(|parent| parent.kind() == "dependent_type")
17102        else {
17103            return false;
17104        };
17105        return dependent
17106            .child(0)
17107            .is_some_and(|keyword| keyword.kind() == "typename");
17108    }
17109    let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
17110        return false;
17111    };
17112    if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
17113        return false;
17114    }
17115    let Some(declarator) = error
17116        .parent()
17117        .filter(|parent| parent.kind() == "parenthesized_declarator")
17118    else {
17119        return false;
17120    };
17121    let Some(declaration) = declarator
17122        .parent()
17123        .filter(|parent| parent.kind() == "declaration")
17124    else {
17125        return false;
17126    };
17127    if declaration.child_by_field_name("declarator") != Some(declarator) {
17128        return false;
17129    }
17130    let Some(type_node) = declaration.child_by_field_name("type") else {
17131        return false;
17132    };
17133    type_node.kind() == "dependent_type"
17134        && type_node
17135            .child(0)
17136            .is_some_and(|keyword| keyword.kind() == "typename")
17137}
17138
17139fn recovered_c_explicit_assignment_callee(
17140    visibility: &VisibilityIndex<'_>,
17141    file: &ProjectFile,
17142    node: Node<'_>,
17143    name: &str,
17144) -> bool {
17145    let mut current = node;
17146    let error = loop {
17147        let Some(parent) = current.parent() else {
17148            return false;
17149        };
17150        if parent.is_error() {
17151            break parent;
17152        }
17153        current = parent;
17154    };
17155    let mut cursor = error.walk();
17156    let explicit_recovery_precedes_callee = error
17157        .named_children(&mut cursor)
17158        .take_while(|child| child.start_byte() < node.start_byte())
17159        .any(|child| child.kind() == "explicit_function_specifier");
17160    if !explicit_recovery_precedes_callee {
17161        return false;
17162    }
17163    visibility
17164        .visible_identifier_candidates(file, name)
17165        .any(CodeUnit::is_function)
17166}
17167
17168fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
17169    while let Some(parent) = node.parent() {
17170        if matches!(
17171            parent.kind(),
17172            "preproc_def" | "preproc_function_def" | "preproc_params"
17173        ) {
17174            return true;
17175        }
17176        if parent.is_error()
17177            || matches!(
17178                parent.kind(),
17179                "translation_unit" | "function_definition" | "compound_statement"
17180            )
17181        {
17182            return false;
17183        }
17184        node = parent;
17185    }
17186    false
17187}
17188
17189fn recovered_c_label_role(node: Node<'_>) -> bool {
17190    node.parent().is_some_and(|parent| {
17191        matches!(parent.kind(), "labeled_statement" | "goto_statement")
17192            && parent.child_by_field_name("label") == Some(node)
17193    })
17194}
17195
17196fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
17197    while let Some(parent) = node.parent() {
17198        if parent.is_error() {
17199            return false;
17200        }
17201        // A C macro call recovered as a function declarator can parse an
17202        // assignment-shaped argument as an optional parameter. Its
17203        // `default_value` field remains an expression role even though the
17204        // surrounding call shape is beneath ERROR.
17205        if parent.kind() == "optional_parameter_declaration"
17206            && parent
17207                .child_by_field_name("default_value")
17208                .is_some_and(|value| node_range_contains(value, node))
17209        {
17210            return true;
17211        }
17212        if parent.kind().ends_with("_expression")
17213            || matches!(
17214                parent.kind(),
17215                "argument_list"
17216                    | "return_statement"
17217                    | "expression_statement"
17218                    | "case_statement"
17219                    | "initializer_list"
17220                    | "init_declarator"
17221                    | "array_declarator"
17222                    | "field_designator"
17223                    | "enumerator"
17224            )
17225        {
17226            return true;
17227        }
17228        if matches!(
17229            parent.kind(),
17230            "translation_unit"
17231                | "function_definition"
17232                | "compound_statement"
17233                | "declaration"
17234                | "field_declaration"
17235                | "parameter_declaration"
17236        ) {
17237            return false;
17238        }
17239        node = parent;
17240    }
17241    false
17242}
17243
17244/// Whether a parameter declaration belongs to the callable scope whose body can
17245/// contain references to it.
17246///
17247/// Error recovery can wrap a macro-decorated class body in a synthetic outer
17248/// `function_definition`. Merely finding any callable ancestor would then leak
17249/// parameters from member prototypes into later member bodies. Require the
17250/// parameter to be inside that definition's own declarator instead.
17251pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
17252    let mut current = parameter.parent();
17253    while let Some(ancestor) = current {
17254        if ancestor.kind() == "lambda_expression" {
17255            return ancestor
17256                .child_by_field_name("declarator")
17257                .is_some_and(|declarator| {
17258                    declarator.start_byte() <= parameter.start_byte()
17259                        && parameter.end_byte() <= declarator.end_byte()
17260                });
17261        }
17262        if ancestor.kind() == "function_definition" {
17263            return ancestor
17264                .child_by_field_name("declarator")
17265                .is_some_and(|declarator| {
17266                    declarator.start_byte() <= parameter.start_byte()
17267                        && parameter.end_byte() <= declarator.end_byte()
17268                });
17269        }
17270        current = ancestor.parent();
17271    }
17272    false
17273}
17274
17275pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
17276    let mut current = node.parent();
17277    while let Some(ancestor) = current {
17278        if matches!(
17279            ancestor.kind(),
17280            "parameter_declaration" | "optional_parameter_declaration"
17281        ) {
17282            return crate::declarations::cpp_type_field_contains_node(ancestor, node);
17283        }
17284        if matches!(
17285            ancestor.kind(),
17286            "function_definition" | "lambda_expression" | "compound_statement"
17287        ) {
17288            return false;
17289        }
17290        current = ancestor.parent();
17291    }
17292    false
17293}
17294
17295/// Walk tag-specifier ancestors until a type-reference context makes its name
17296/// an operand rather than a declaration. Callers retain their own terminal
17297/// declaration boundaries after this shared exclusion scan.
17298pub fn cpp_tag_specifier_ancestors_before_type_reference_context(
17299    specifier: Node<'_>,
17300) -> impl Iterator<Item = Node<'_>> {
17301    std::iter::successors(specifier.parent(), |ancestor| ancestor.parent()).take_while(|ancestor| {
17302        !matches!(
17303            ancestor.kind(),
17304            "type_descriptor"
17305                | "parameter_declaration"
17306                | "optional_parameter_declaration"
17307                | "template_argument_list"
17308                | "cast_expression"
17309        )
17310    })
17311}
17312
17313pub fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
17314    if specifier.child_by_field_name("body").is_some() {
17315        return true;
17316    }
17317    for ancestor in cpp_tag_specifier_ancestors_before_type_reference_context(specifier) {
17318        match ancestor.kind() {
17319            "declaration" | "field_declaration" => {
17320                let mut cursor = ancestor.walk();
17321                return ancestor
17322                    .children_by_field_name("declarator", &mut cursor)
17323                    .next()
17324                    .is_none();
17325            }
17326            "translation_unit" => return true,
17327            _ => {}
17328        }
17329    }
17330    false
17331}
17332
17333pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
17334    match node.kind() {
17335        "identifier"
17336        | "field_identifier"
17337        | "qualified_identifier"
17338        | "scoped_identifier"
17339        | "operator_name"
17340        | "destructor_name"
17341        | "literal_operator_name" => Some(node),
17342        "reference_declarator" | "parenthesized_declarator" => {
17343            node.named_child(0).and_then(declarator_name_node)
17344        }
17345        _ => node
17346            .child_by_field_name("declarator")
17347            .or_else(|| node.child_by_field_name("name"))
17348            .or_else(|| node.child_by_field_name("field"))
17349            .and_then(declarator_name_node),
17350    }
17351}
17352
17353/// Whether `candidate` occurs on the declarator path before its selected name.
17354pub fn declarator_path_contains_before_name(
17355    declarator: Node<'_>,
17356    name: Node<'_>,
17357    candidate: Node<'_>,
17358) -> bool {
17359    let mut current = Some(declarator);
17360    while let Some(node) = current {
17361        if same_node(node, candidate) {
17362            return true;
17363        }
17364        if same_node(node, name) {
17365            return false;
17366        }
17367        current = node
17368            .child_by_field_name("declarator")
17369            .or_else(|| node.child_by_field_name("name"))
17370            .or_else(|| node.child_by_field_name("field"));
17371    }
17372    false
17373}
17374
17375fn declarator_name_path_contains(
17376    declarator: Node<'_>,
17377    candidate: Node<'_>,
17378    allow_type_identifier: bool,
17379) -> bool {
17380    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
17381        return false;
17382    };
17383    declarator_path_contains_before_name(declarator, name, candidate)
17384}
17385
17386fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
17387    match node.kind() {
17388        "identifier"
17389        | "field_identifier"
17390        | "operator_name"
17391        | "destructor_name"
17392        | "literal_operator_name" => Some(node),
17393        "type_identifier" if allow_type_identifier => Some(node),
17394        _ => node
17395            .child_by_field_name("declarator")
17396            .or_else(|| node.child_by_field_name("name"))
17397            .or_else(|| node.child_by_field_name("field"))
17398            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
17399    }
17400}
17401
17402/// True when `node` is a component of a larger structured type node whose outer
17403/// range is the single reference surfaced to callers.
17404pub fn is_nested_type_node(node: Node<'_>) -> bool {
17405    node.parent().is_some_and(|parent| {
17406        matches!(
17407            parent.kind(),
17408            "qualified_identifier" | "scoped_type_identifier" | "template_type"
17409        )
17410    })
17411}
17412
17413pub struct OutOfLineMemberDefinitionOwners<'tree> {
17414    pub owners: Vec<(Node<'tree>, CodeUnit)>,
17415    innermost: Option<(Node<'tree>, CodeUnit)>,
17416}
17417
17418impl OutOfLineMemberDefinitionOwners<'_> {
17419    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
17420        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
17421    }
17422}
17423
17424pub struct QualifiedOwnerComponents<'tree> {
17425    pub nodes: Vec<Node<'tree>>,
17426    pub names: Vec<String>,
17427    pub global: bool,
17428}
17429
17430/// True when each structured qualifier on the callable-name path has a real
17431/// `::` token. A macro-prefixed return type can make tree-sitter insert a
17432/// zero-width missing separator and parse `TYPE Result<T> method()` as the
17433/// false qualified declarator `Result<T>::method`.
17434pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
17435    let mut stack = vec![node];
17436    let mut found_separator = false;
17437    while let Some(current) = stack.pop() {
17438        if !matches!(
17439            current.kind(),
17440            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17441        ) {
17442            continue;
17443        }
17444        let mut current_has_separator = false;
17445        for child in children_iter(current) {
17446            if child.kind() == "::" {
17447                if child.is_missing() {
17448                    return false;
17449                }
17450                current_has_separator = true;
17451                found_separator = true;
17452            }
17453        }
17454        if !current_has_separator {
17455            return false;
17456        }
17457        for field in ["scope", "name"] {
17458            if let Some(child) = current.child_by_field_name(field)
17459                && matches!(
17460                    child.kind(),
17461                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17462                )
17463            {
17464                stack.push(child);
17465            }
17466        }
17467    }
17468    found_separator
17469}
17470
17471pub fn qualified_owner_components<'tree>(
17472    node: Node<'tree>,
17473    source: &str,
17474) -> Option<QualifiedOwnerComponents<'tree>> {
17475    if !qualified_name_has_concrete_scope_separators(node) {
17476        return None;
17477    }
17478    let mut nodes = cpp_name_component_nodes(node)?;
17479    nodes.pop()?;
17480    if nodes.is_empty() {
17481        return None;
17482    }
17483    let names = nodes
17484        .iter()
17485        .map(|component| node_text(*component, source).to_string())
17486        .collect();
17487    Some(QualifiedOwnerComponents {
17488        nodes,
17489        names,
17490        global: is_globally_qualified_cpp_name(node),
17491    })
17492}
17493
17494pub fn out_of_line_member_definition_owner<'tree>(
17495    analyzer: &CppGraphSource<'_>,
17496    visibility: &VisibilityIndex<'_>,
17497    file: &ProjectFile,
17498    source: &str,
17499    node: Node<'tree>,
17500) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
17501    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
17502        || !has_ancestor_kind(node, "function_definition")
17503        || !is_function_declarator_name_root(node)
17504    {
17505        return None;
17506    }
17507    let qualified = qualified_owner_components(node, source)?;
17508    let lexical_scope = enclosing_namespace_components(node, source)?;
17509    let mut owners = Vec::new();
17510    let mut innermost = None;
17511
17512    for component_count in 1..=qualified.names.len() {
17513        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
17514            .resolve_type_components_lexically(
17515                analyzer,
17516                file,
17517                &qualified.names[..component_count],
17518                qualified.global,
17519                &lexical_scope,
17520            )
17521            && !owners
17522                .iter()
17523                .any(|(_, existing)| same_visible_symbol(existing, &unit))
17524        {
17525            if component_count == qualified.names.len() {
17526                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
17527            }
17528            owners.push((qualified.nodes[component_count - 1], unit));
17529        }
17530    }
17531
17532    // The C++ analyzer has already reconciled an indexed out-of-line callable
17533    // against the include-visible class table. Consult that canonical owner
17534    // chain only when ordinary lexical lookup could not recover the innermost
17535    // owner.  A one-segment qualifier is safe here only when the enclosing
17536    // indexed callable has an authoritative class owner and the parser's
17537    // namespace path is a (possibly sparse) subsequence of that owner path.
17538    // The latter is what lets macro-wrapped namespace sentinels recover a
17539    // missing `time_internal`/`cord_internal` component without guessing an
17540    // unrelated short name.
17541    if innermost.is_none() {
17542        let indexed_owner_components = visibility
17543            .indexed_enclosing_owner_scope(analyzer, file, node)
17544            .or_else(|| {
17545                // Retain the legacy rendered-name fallback for the existing
17546                // multi-segment path when an enclosing owner chain is not
17547                // available (for example, cache-loaded units without parent
17548                // links).  One-segment recovery must stay canonical-only.
17549                if qualified.names.len() <= 1 {
17550                    return None;
17551                }
17552                let range = Range {
17553                    start_byte: node.start_byte(),
17554                    end_byte: node.end_byte(),
17555                    start_line: node.start_position().row,
17556                    end_line: node.end_position().row,
17557                };
17558                let start = analyzer.enclosing_code_unit(file, &range)?;
17559                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17560                    brokk_bifrost_core::analyzer::Language::Cpp,
17561                    &cpp_name_for(&start),
17562                );
17563                components.pop();
17564                Some(components)
17565            });
17566        if let Some(indexed_owner_components) = indexed_owner_components
17567            && indexed_owner_components.len() > qualified.names.len()
17568            && indexed_owner_components.ends_with(&qualified.names)
17569            && indexed_namespace_path_is_recoverable(
17570                &lexical_scope,
17571                &indexed_owner_components,
17572                qualified.names.len(),
17573            )
17574            // A globally-qualified one-segment owner is an explicit request
17575            // for the top-level binding; do not reinterpret it as a missing
17576            // namespace component.  Existing multi-segment global lookups
17577            // retain their historical indexed recovery.
17578            && (qualified.names.len() > 1 || !qualified.global)
17579        {
17580            let namespace_count = indexed_owner_components.len() - qualified.names.len();
17581            for component_count in 1..=qualified.names.len() {
17582                let expected = &indexed_owner_components[..namespace_count + component_count];
17583                let owner_node = qualified.nodes[component_count - 1];
17584                for owner in visibility
17585                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
17586                    .filter(|candidate| candidate.is_class())
17587                    .filter(|candidate| {
17588                        canonical_cpp_scope_components(candidate) == expected
17589                            && visibility.external_type_candidate_visible_in_context(
17590                                analyzer, file, candidate, node,
17591                            )
17592                    })
17593                {
17594                    if component_count == qualified.names.len() && innermost.is_none() {
17595                        innermost = Some((owner_node, owner.clone()));
17596                    }
17597                    if !owners
17598                        .iter()
17599                        .any(|(_, existing)| same_symbol(existing, owner))
17600                    {
17601                        owners.push((owner_node, owner.clone()));
17602                    }
17603                }
17604            }
17605        }
17606    }
17607    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
17608}
17609
17610fn is_function_declarator_name_root(node: Node<'_>) -> bool {
17611    let mut current = node;
17612    while let Some(parent) = current.parent() {
17613        if parent.kind() == "function_declarator" {
17614            return parent.child_by_field_name("declarator") == Some(current);
17615        }
17616        if matches!(
17617            parent.kind(),
17618            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
17619        ) && parent.child_by_field_name("declarator") == Some(current)
17620        {
17621            current = parent;
17622            continue;
17623        }
17624        return false;
17625    }
17626    false
17627}
17628
17629pub fn append_cpp_name_components(
17630    node: Node<'_>,
17631    source: &str,
17632    out: &mut Vec<String>,
17633) -> Option<()> {
17634    out.extend(
17635        cpp_name_component_nodes(node)?
17636            .into_iter()
17637            .map(|component| node_text(component, source).to_string()),
17638    );
17639    Some(())
17640}
17641
17642/// The components of a C++ name with the source range of the token that
17643/// writes each one, outermost first.
17644fn cpp_name_component_ranges(node: Node<'_>, source: &str) -> Option<Vec<(String, Range)>> {
17645    cpp_name_component_nodes(node).map(|components| {
17646        components
17647            .into_iter()
17648            .map(|component| {
17649                (
17650                    node_text(component, source).to_string(),
17651                    node_range(component),
17652                )
17653            })
17654            .collect()
17655    })
17656}
17657
17658pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17659    let mut components = Vec::new();
17660    append_cpp_name_components(node, source, &mut components)?;
17661    Some(components)
17662}
17663
17664/// Resolve a structured type spelling from an object-like macro replacement
17665/// when definition-site source order has no answer.
17666///
17667/// Macro replacement tokens are looked up where the macro is expanded, so a
17668/// type declared later in the defining header can still be their destination.
17669/// Without expanding every invocation, accept only one include-visible logical
17670/// class or alias whose structured path ends in the replacement components.
17671/// An ordinary lexical answer always takes precedence at the call site.
17672pub fn unique_macro_replacement_type_candidate(
17673    analyzer: &CppGraphSource<'_>,
17674    visibility: &VisibilityIndex<'_>,
17675    file: &ProjectFile,
17676    components: &[String],
17677) -> Option<CodeUnit> {
17678    let terminal = components.last()?;
17679    let mut candidates = Vec::new();
17680    for candidate in visibility
17681        .visible_identifier_candidates(file, terminal)
17682        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
17683        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
17684    {
17685        if !candidates
17686            .iter()
17687            .any(|existing| same_logical_symbol(existing, candidate))
17688        {
17689            candidates.push(candidate.clone());
17690        }
17691    }
17692    (candidates.len() == 1).then(|| candidates.remove(0))
17693}
17694
17695/// Whether a structured using-declaration scope can name `qualified` as an
17696/// ancestor class. The boundary check prevents `Base` from matching
17697/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
17698pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
17699    qualified == scope
17700        || qualified
17701            .strip_suffix(scope)
17702            .is_some_and(|prefix| prefix.ends_with("::"))
17703}
17704
17705/// Whether `node` is the direct structured type payload of a template
17706/// argument. This role remains meaningful even when a surrounding expression
17707/// is below tree-sitter recovery, because both the `template_argument_list`
17708/// and the `type_descriptor` retain their named fields.
17709pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
17710    let Some(type_descriptor) = node.parent() else {
17711        return false;
17712    };
17713    if type_descriptor.kind() != "type_descriptor"
17714        || type_descriptor.child_by_field_name("type") != Some(node)
17715    {
17716        return false;
17717    }
17718    let Some(arguments) = type_descriptor.parent() else {
17719        return false;
17720    };
17721    if arguments.kind() != "template_argument_list" {
17722        return false;
17723    }
17724    arguments.parent().is_some_and(|parent| {
17725        matches!(parent.kind(), "template_type" | "template_function")
17726            && parent.child_by_field_name("arguments") == Some(arguments)
17727    })
17728}
17729
17730pub fn cpp_template_reference_arguments(
17731    mut node: Node<'_>,
17732    source: &str,
17733) -> Option<Vec<CppTemplateExpression>> {
17734    loop {
17735        match node.kind() {
17736            "template_type" | "template_function" => {
17737                let arguments = node.child_by_field_name("arguments")?;
17738                let mut cursor = arguments.walk();
17739                return Some(
17740                    arguments
17741                        .named_children(&mut cursor)
17742                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
17743                        .map(|argument| CppTemplateExpression {
17744                            text: normalize_cpp_whitespace(node_text(argument, source)),
17745                            // One template term from a resolver query; see `ParentIndex::unindexed`.
17746                            term: cpp_template_term(
17747                                argument,
17748                                source,
17749                                &[],
17750                                &ParentIndex::unindexed(),
17751                            ),
17752                        })
17753                        .collect(),
17754                );
17755            }
17756            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
17757                node = node
17758                    .child_by_field_name("name")
17759                    .or_else(|| node.child_by_field_name("type"))?;
17760            }
17761            _ => return None,
17762        }
17763    }
17764}
17765
17766fn cpp_reconcile_primary_template_parameters(
17767    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
17768    preferred: &CodeUnit,
17769) -> Option<Vec<CppTemplateParameterMetadata>> {
17770    let canonical = candidates
17771        .iter()
17772        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
17773    let mut merged = canonical
17774        .parameters
17775        .iter()
17776        .map(|parameter| CppTemplateParameterMetadata {
17777            name: parameter.name.clone(),
17778            kind: parameter.kind,
17779            variadic: parameter.variadic,
17780            default: None,
17781        })
17782        .collect::<Vec<_>>();
17783
17784    for (_, metadata) in candidates {
17785        if metadata.parameters.len() != merged.len() {
17786            return None;
17787        }
17788        let rename_bindings = metadata
17789            .parameters
17790            .iter()
17791            .zip(&merged)
17792            .map(|(parameter, canonical)| {
17793                (
17794                    parameter.name.clone(),
17795                    CppTemplateTerm::Parameter(canonical.name.clone()),
17796                )
17797            })
17798            .collect::<HashMap<_, _>>();
17799        for ((parameter, canonical), merged_parameter) in metadata
17800            .parameters
17801            .iter()
17802            .zip(&canonical.parameters)
17803            .zip(&mut merged)
17804        {
17805            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
17806                return None;
17807            }
17808            let Some(default) = &parameter.default else {
17809                continue;
17810            };
17811            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
17812            if let Some(existing) = &merged_parameter.default {
17813                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
17814                    return None;
17815                }
17816            } else {
17817                merged_parameter.default = Some(CppTemplateExpression {
17818                    text: default.text.clone(),
17819                    term: normalized_term,
17820                });
17821            }
17822        }
17823    }
17824    Some(merged)
17825}
17826
17827pub fn cpp_bind_template_arguments(
17828    parameters: &[CppTemplateParameterMetadata],
17829    explicit_arguments: &[CppTemplateExpression],
17830) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
17831    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
17832    if variadic_index.is_some_and(|index| {
17833        index + 1 != parameters.len()
17834            || parameters[index + 1..]
17835                .iter()
17836                .any(|parameter| parameter.variadic)
17837    }) {
17838        return None;
17839    }
17840    let fixed_count = variadic_index.unwrap_or(parameters.len());
17841    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
17842        return None;
17843    }
17844    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
17845    let mut expanded = explicit_arguments[..explicit_fixed_count]
17846        .iter()
17847        .map(cpp_clone_template_expression_iterative)
17848        .collect::<Vec<_>>();
17849    let mut bindings = HashMap::default();
17850    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
17851        bindings.insert(
17852            parameter.name.clone(),
17853            cpp_clone_template_term_iterative(&argument.term),
17854        );
17855    }
17856    for parameter in &parameters[explicit_fixed_count..fixed_count] {
17857        let default = parameter.default.as_ref()?;
17858        let term = cpp_substitute_template_term(&default.term, &bindings)?;
17859        bindings.insert(parameter.name.clone(), term.clone());
17860        expanded.push(CppTemplateExpression {
17861            text: default.text.clone(),
17862            term,
17863        });
17864    }
17865    if let Some(index) = variadic_index {
17866        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
17867        expanded.extend(
17868            packed_arguments
17869                .iter()
17870                .map(cpp_clone_template_expression_iterative),
17871        );
17872        bindings.insert(
17873            parameters[index].name.clone(),
17874            CppTemplateTerm::Node {
17875                kind: "parameter_pack".to_string(),
17876                children: packed_arguments
17877                    .iter()
17878                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
17879                    .collect(),
17880            },
17881        );
17882    }
17883    Some((expanded, bindings))
17884}
17885
17886fn cpp_specialization_matches(
17887    metadata: &CppTemplateMetadata,
17888    arguments: &[CppTemplateExpression],
17889) -> bool {
17890    cpp_specialization_terms_match(
17891        &metadata.parameters,
17892        &metadata.specialization_arguments,
17893        arguments,
17894    )
17895}
17896
17897fn cpp_specialization_terms_match(
17898    parameters: &[CppTemplateParameterMetadata],
17899    patterns: &[CppTemplateExpression],
17900    arguments: &[CppTemplateExpression],
17901) -> bool {
17902    if patterns.len() != arguments.len() {
17903        return false;
17904    }
17905    let parameter_names = parameters
17906        .iter()
17907        .map(|parameter| parameter.name.as_str())
17908        .collect::<HashSet<_>>();
17909    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
17910    patterns.iter().zip(arguments).all(|(pattern, argument)| {
17911        cpp_unify_template_term(
17912            &pattern.term,
17913            &argument.term,
17914            &parameter_names,
17915            &mut bindings,
17916        )
17917    })
17918}
17919
17920fn cpp_specialization_more_specialized(
17921    candidate: &CppTemplateMetadata,
17922    other: &CppTemplateMetadata,
17923) -> bool {
17924    cpp_specialization_pattern_accepts(other, candidate)
17925        && !cpp_specialization_pattern_accepts(candidate, other)
17926}
17927
17928fn cpp_specialization_pattern_accepts(
17929    broader: &CppTemplateMetadata,
17930    narrower: &CppTemplateMetadata,
17931) -> bool {
17932    cpp_specialization_terms_match(
17933        &broader.parameters,
17934        &broader.specialization_arguments,
17935        &narrower.specialization_arguments,
17936    )
17937}
17938
17939pub fn cpp_substitute_template_term(
17940    term: &CppTemplateTerm,
17941    bindings: &HashMap<String, CppTemplateTerm>,
17942) -> Option<CppTemplateTerm> {
17943    enum Work<'a> {
17944        Visit(&'a CppTemplateTerm),
17945        Build { kind: String, child_count: usize },
17946    }
17947
17948    let mut work = vec![Work::Visit(term)];
17949    let mut substituted = Vec::new();
17950    while let Some(next) = work.pop() {
17951        match next {
17952            Work::Visit(CppTemplateTerm::Parameter(name)) => {
17953                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
17954            }
17955            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17956                substituted.push(CppTemplateTerm::Atom {
17957                    kind: kind.clone(),
17958                    text: text.clone(),
17959                });
17960            }
17961            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17962                work.push(Work::Build {
17963                    kind: kind.clone(),
17964                    child_count: children.len(),
17965                });
17966                work.extend(children.iter().rev().map(Work::Visit));
17967            }
17968            Work::Build { kind, child_count } => {
17969                let children = substituted.split_off(substituted.len() - child_count);
17970                substituted.push(CppTemplateTerm::Node { kind, children });
17971            }
17972        }
17973    }
17974    substituted.pop()
17975}
17976
17977pub fn cpp_substitute_template_arguments(
17978    arguments: &[CppTemplateExpression],
17979    bindings: &HashMap<String, CppTemplateTerm>,
17980) -> Option<Vec<CppTemplateExpression>> {
17981    let mut substituted = Vec::new();
17982    for argument in arguments {
17983        let CppTemplateTerm::Node { kind, children } = &argument.term else {
17984            substituted.push(CppTemplateExpression {
17985                text: argument.text.clone(),
17986                term: cpp_substitute_template_term(&argument.term, bindings)?,
17987            });
17988            continue;
17989        };
17990        if kind != "parameter_pack_expansion" {
17991            substituted.push(CppTemplateExpression {
17992                text: argument.text.clone(),
17993                term: cpp_substitute_template_term(&argument.term, bindings)?,
17994            });
17995            continue;
17996        }
17997        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
17998            return None;
17999        };
18000        if ellipsis != "..." {
18001            return None;
18002        }
18003
18004        let mut pack_names = Vec::new();
18005        let mut work = vec![pattern];
18006        while let Some(term) = work.pop() {
18007            match term {
18008                CppTemplateTerm::Parameter(name)
18009                    if matches!(
18010                        bindings.get(name),
18011                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
18012                    ) =>
18013                {
18014                    if !pack_names.contains(name) {
18015                        pack_names.push(name.clone());
18016                    }
18017                }
18018                CppTemplateTerm::Node { children, .. } => work.extend(children),
18019                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
18020            }
18021        }
18022        let first_pack = pack_names.first()?;
18023        let CppTemplateTerm::Node {
18024            children: first_elements,
18025            ..
18026        } = bindings.get(first_pack)?
18027        else {
18028            return None;
18029        };
18030        let pack_len = first_elements.len();
18031        for pack_name in &pack_names {
18032            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
18033                return None;
18034            };
18035            if children.len() != pack_len {
18036                return None;
18037            }
18038        }
18039        for index in 0..pack_len {
18040            let mut element_bindings = bindings.clone();
18041            for pack_name in &pack_names {
18042                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
18043                    return None;
18044                };
18045                element_bindings.insert(
18046                    pack_name.clone(),
18047                    cpp_clone_template_term_iterative(&children[index]),
18048                );
18049            }
18050            substituted.push(CppTemplateExpression {
18051                text: argument.text.clone(),
18052                term: cpp_substitute_template_term(pattern, &element_bindings)?,
18053            });
18054        }
18055    }
18056    Some(substituted)
18057}
18058
18059fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
18060    enum Work<'a> {
18061        Visit(&'a CppTemplateTerm),
18062        Build { kind: String, child_count: usize },
18063    }
18064
18065    let mut work = vec![Work::Visit(term)];
18066    let mut cloned = Vec::new();
18067    while let Some(next) = work.pop() {
18068        match next {
18069            Work::Visit(CppTemplateTerm::Parameter(name)) => {
18070                cloned.push(CppTemplateTerm::Parameter(name.clone()));
18071            }
18072            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
18073                cloned.push(CppTemplateTerm::Atom {
18074                    kind: kind.clone(),
18075                    text: text.clone(),
18076                });
18077            }
18078            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
18079                work.push(Work::Build {
18080                    kind: kind.clone(),
18081                    child_count: children.len(),
18082                });
18083                work.extend(children.iter().rev().map(Work::Visit));
18084            }
18085            Work::Build { kind, child_count } => {
18086                let children = cloned.split_off(cloned.len() - child_count);
18087                cloned.push(CppTemplateTerm::Node { kind, children });
18088            }
18089        }
18090    }
18091    cloned
18092        .pop()
18093        .expect("template term traversal emits one root")
18094}
18095
18096fn cpp_clone_template_expression_iterative(
18097    expression: &CppTemplateExpression,
18098) -> CppTemplateExpression {
18099    CppTemplateExpression {
18100        text: expression.text.clone(),
18101        term: cpp_clone_template_term_iterative(&expression.term),
18102    }
18103}
18104
18105pub fn cpp_unify_template_term(
18106    pattern: &CppTemplateTerm,
18107    argument: &CppTemplateTerm,
18108    parameters: &HashSet<&str>,
18109    bindings: &mut HashMap<String, CppTemplateTerm>,
18110) -> bool {
18111    let mut work = vec![(pattern, argument)];
18112    while let Some((pattern, argument)) = work.pop() {
18113        match pattern {
18114            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
18115                if let Some(bound) = bindings.get(name) {
18116                    if !cpp_template_terms_equal(bound, argument) {
18117                        return false;
18118                    }
18119                } else {
18120                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
18121                }
18122            }
18123            CppTemplateTerm::Atom {
18124                kind: pattern_kind,
18125                text: pattern_text,
18126            } => {
18127                if !matches!(
18128                    argument,
18129                    CppTemplateTerm::Atom { kind, text }
18130                        if kind == pattern_kind && text == pattern_text
18131                ) {
18132                    return false;
18133                }
18134            }
18135            CppTemplateTerm::Node {
18136                kind: pattern_kind,
18137                children: pattern_children,
18138            } => {
18139                let CppTemplateTerm::Node { kind, children } = argument else {
18140                    return false;
18141                };
18142                if kind != pattern_kind || children.len() != pattern_children.len() {
18143                    return false;
18144                }
18145                work.extend(pattern_children.iter().zip(children).rev());
18146            }
18147            CppTemplateTerm::Parameter(_) => return false,
18148        }
18149    }
18150    true
18151}
18152
18153pub(crate) fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
18154    let mut work = vec![(left, right)];
18155    while let Some((left, right)) = work.pop() {
18156        match (left, right) {
18157            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
18158                if left != right {
18159                    return false;
18160                }
18161            }
18162            (
18163                CppTemplateTerm::Atom {
18164                    kind: left_kind,
18165                    text: left_text,
18166                },
18167                CppTemplateTerm::Atom {
18168                    kind: right_kind,
18169                    text: right_text,
18170                },
18171            ) => {
18172                if left_kind != right_kind || left_text != right_text {
18173                    return false;
18174                }
18175            }
18176            (
18177                CppTemplateTerm::Node {
18178                    kind: left_kind,
18179                    children: left_children,
18180                },
18181                CppTemplateTerm::Node {
18182                    kind: right_kind,
18183                    children: right_children,
18184                },
18185            ) => {
18186                if left_kind != right_kind || left_children.len() != right_children.len() {
18187                    return false;
18188                }
18189                work.extend(left_children.iter().zip(right_children).rev());
18190            }
18191            _ => return false,
18192        }
18193    }
18194    true
18195}
18196
18197pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
18198    let mut components = Vec::new();
18199    let mut stack = vec![node];
18200    while let Some(current) = stack.pop() {
18201        match current.kind() {
18202            "identifier"
18203            | "field_identifier"
18204            | "namespace_identifier"
18205            | "type_identifier"
18206            | "operator_name"
18207            | "destructor_name" => components.push(current),
18208            "template_type" | "template_function" => {
18209                stack.push(current.child_by_field_name("name")?);
18210            }
18211            "dependent_name" => stack.push(current.named_child(0)?),
18212            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
18213                stack.push(current.child_by_field_name("name")?);
18214                if let Some(scope) = current.child_by_field_name("scope") {
18215                    stack.push(scope);
18216                }
18217            }
18218            "nested_namespace_specifier" => {
18219                for index in (0..current.named_child_count()).rev() {
18220                    stack.push(current.named_child(index)?);
18221                }
18222            }
18223            _ => return None,
18224        }
18225    }
18226    Some(components)
18227}
18228
18229pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
18230    node.child_by_field_name("scope").is_none()
18231        && node.child(0).is_some_and(|child| child.kind() == "::")
18232}
18233
18234fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
18235    let mut namespaces = Vec::new();
18236    for parent in enclosing_cpp_namespace_nodes(node) {
18237        if let Some(name) = parent.child_by_field_name("name") {
18238            let mut components = Vec::new();
18239            append_cpp_name_components(name, source, &mut components)?;
18240            namespaces.push(components);
18241        }
18242    }
18243    Some(namespaces.into_iter().flatten().collect())
18244}
18245
18246/// Whether a parser-derived namespace path can be reconciled with an indexed
18247/// owner scope without inventing an unrelated short-name binding.
18248///
18249/// Macro namespace sentinels can make tree-sitter omit one or more namespace
18250/// definitions from the ancestor chain. Preserve the order of every namespace
18251/// that did survive parsing, but allow indexed components between them. An
18252/// empty path is accepted only when the declarator itself supplies a nested
18253/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
18254/// owner chain, that suffix is structural evidence that a namespace was lost.
18255/// A one-segment owner at the translation-unit root remains insufficient.
18256fn indexed_namespace_path_is_recoverable(
18257    lexical_scope: &[String],
18258    indexed_owner_scope: &[String],
18259    explicit_owner_component_count: usize,
18260) -> bool {
18261    if lexical_scope.is_empty() {
18262        return explicit_owner_component_count > 1;
18263    }
18264    if lexical_scope.len() >= indexed_owner_scope.len() {
18265        return false;
18266    }
18267    super::cpp_namespace_path_is_ordered_subsequence(lexical_scope, indexed_owner_scope)
18268}
18269
18270pub use brokk_bifrost_core::analyzer::tree_walk::has_ancestor_kind;
18271
18272/// Whether a declaration type is initialized with a pointer cast.
18273///
18274/// This structured shape has an independent qualified occurrence in addition
18275/// to the cast descriptor below it. Other declarations must keep their normal
18276/// full-range occurrence only.
18277pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
18278    let mut current = Some(node);
18279    while let Some(candidate) = current {
18280        if candidate.kind() == "declaration" {
18281            if !crate::declarations::cpp_type_field_contains_node(candidate, node) {
18282                return false;
18283            }
18284            let mut cursor = candidate.walk();
18285            return candidate.named_children(&mut cursor).any(|child| {
18286                child.kind() == "init_declarator"
18287                    && child
18288                        .child_by_field_name("value")
18289                        .is_some_and(|value| value.kind() == "cast_expression")
18290            });
18291        }
18292        current = candidate.parent();
18293    }
18294    false
18295}
18296
18297#[derive(Clone, Copy, PartialEq, Eq)]
18298pub(crate) enum QualifiedAliasReferenceKind {
18299    Ordinary,
18300    ConstructorWithExpressionArgument,
18301    ExhaustiveTemplate,
18302}
18303
18304/// Whether a qualified alias reference preserves the requested target.
18305///
18306/// The complete qualified spelling and its terminal identifier are both valid
18307/// occurrences when the visible alias path is structurally proven to name the
18308/// target. Template aliases use their bound arguments; ordinary aliases use
18309/// their structured primary chain.
18310pub(crate) fn qualified_alias_reference_preserves_target(
18311    node: Node<'_>,
18312    target: &CodeUnit,
18313    analyzer: &CppGraphSource<'_>,
18314    visibility: &VisibilityIndex<'_>,
18315    file: &ProjectFile,
18316    source: &str,
18317) -> Option<QualifiedAliasReferenceKind> {
18318    if !matches!(
18319        node.kind(),
18320        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
18321    ) {
18322        return None;
18323    }
18324    let components = cpp_type_name_components(node, source)?;
18325    let name = components.last()?;
18326    analyzer.type_alias_provider().and_then(|provider| {
18327        visibility
18328            .visible_identifier_candidates(file, name)
18329            .find_map(|candidate| {
18330                let proof = provider.is_type_alias(candidate)
18331                    && canonical_cpp_scope_components(candidate) == components
18332                    && visibility.external_type_candidate_visible_in_context(
18333                        analyzer, file, candidate, node,
18334                    )
18335                    && match cpp_template_reference_arguments(node, source) {
18336                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
18337                            analyzer, file, candidate, &arguments, target,
18338                        ),
18339                        None => visibility.structured_alias_primary_preserves_target(
18340                            analyzer, file, candidate, target,
18341                        ),
18342                    };
18343                proof.then(|| {
18344                    if cpp_template_reference_arguments(node, source).is_some()
18345                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
18346                            analyzer, file, candidate,
18347                        )
18348                    {
18349                        QualifiedAliasReferenceKind::ExhaustiveTemplate
18350                    } else if qualified_alias_constructor_has_expression_argument(node)
18351                        || qualified_alias_local_constructor_declaration(node)
18352                    {
18353                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
18354                    } else {
18355                        QualifiedAliasReferenceKind::Ordinary
18356                    }
18357                })
18358            })
18359    })
18360}
18361
18362pub(crate) fn qualified_alias_reference_requires_terminal(
18363    reference: Option<QualifiedAliasReferenceKind>,
18364) -> bool {
18365    matches!(
18366        reference,
18367        Some(
18368            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
18369                | QualifiedAliasReferenceKind::ExhaustiveTemplate
18370        )
18371    )
18372}
18373
18374fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
18375    let Some(declaration) = node.parent().filter(|parent| {
18376        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
18377    }) else {
18378        return false;
18379    };
18380    let mut cursor = declaration.walk();
18381    declaration.named_children(&mut cursor).any(|child| {
18382        child.kind() == "init_declarator"
18383            && child
18384                .child_by_field_name("value")
18385                .filter(|value| value.kind() == "argument_list")
18386                .is_some_and(|arguments| {
18387                    let mut cursor = arguments.walk();
18388                    arguments.named_children(&mut cursor).any(|argument| {
18389                        let is_parameter = matches!(
18390                            argument.kind(),
18391                            "parameter_declaration" | "optional_parameter_declaration"
18392                        );
18393                        if is_parameter {
18394                            argument
18395                                .child_by_field_name("type")
18396                                .is_some_and(|type_node| {
18397                                    type_node.kind() == "type_identifier"
18398                                        && argument.child_by_field_name("declarator").is_none()
18399                                })
18400                        } else {
18401                            !argument.kind().ends_with("_literal")
18402                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
18403                        }
18404                    })
18405                })
18406    })
18407}
18408
18409/// Tree-sitter represents a local C++ direct construction such as
18410/// `Alias value(argument)` as a function declarator. Restrict that recovery to
18411/// declarations inside a compound statement so namespace-scope function
18412/// declarations with the same qualified return type stay full-range only.
18413fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
18414    let Some(declaration) = node.parent().filter(|parent| {
18415        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
18416    }) else {
18417        return false;
18418    };
18419    if declaration
18420        .parent()
18421        .is_none_or(|parent| parent.kind() != "compound_statement")
18422    {
18423        return false;
18424    }
18425    let mut cursor = declaration.walk();
18426    declaration
18427        .named_children(&mut cursor)
18428        .any(|child| child.kind() == "function_declarator")
18429}
18430
18431/// Return the terminal identifier represented by a callable or type callee.
18432///
18433/// Qualified, scoped, template, and field wrappers are traversed through their
18434/// grammar fields so both function calls and type constructions emit the token
18435/// that names the referenced declaration.
18436pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
18437    loop {
18438        let next = match node.kind() {
18439            "qualified_identifier"
18440            | "scoped_identifier"
18441            | "template_method"
18442            | "template_function"
18443            | "template_type" => node.child_by_field_name("name"),
18444            "field_expression" => node.child_by_field_name("field"),
18445            _ => None,
18446        };
18447        let Some(next) = next else {
18448            return node;
18449        };
18450        node = next;
18451    }
18452}
18453
18454#[derive(Clone, Copy)]
18455pub struct RecoveredRelationalTemplateMemberCall<'tree> {
18456    pub receiver: Node<'tree>,
18457    pub member: Node<'tree>,
18458    pub arity: usize,
18459}
18460
18461/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
18462/// nested relational expressions instead of a `template_method` call.
18463///
18464/// The recovery uses only grammar fields: the selected field must be the left
18465/// side of `<`, that expression must be the left side of `>`, and the right
18466/// side of `>` must be the parenthesized call arguments. Semantic callers must
18467/// additionally prove the receiver owner and the member's template status.
18468pub fn recovered_relational_template_member_call(
18469    field: Node<'_>,
18470) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
18471    if field.kind() != "field_expression" {
18472        return None;
18473    }
18474    let receiver = field
18475        .child_by_field_name("argument")
18476        .or_else(|| field.child_by_field_name("object"))?;
18477    let member = field.child_by_field_name("field")?;
18478    let less = field.parent()?;
18479    if less.kind() != "binary_expression"
18480        || less.child_by_field_name("left") != Some(field)
18481        || less
18482            .child_by_field_name("operator")
18483            .is_none_or(|operator| operator.kind() != "<")
18484        || less.child_by_field_name("right").is_none()
18485    {
18486        return None;
18487    }
18488    let greater = less.parent()?;
18489    if greater.kind() != "binary_expression"
18490        || greater.child_by_field_name("left") != Some(less)
18491        || greater
18492            .child_by_field_name("operator")
18493            .is_none_or(|operator| operator.kind() != ">")
18494    {
18495        return None;
18496    }
18497    let arguments = greater.child_by_field_name("right")?;
18498    if arguments.kind() != "parenthesized_expression" {
18499        return None;
18500    }
18501    let arity = parenthesized_call_argument_arity(arguments)?;
18502    Some(RecoveredRelationalTemplateMemberCall {
18503        receiver,
18504        member,
18505        arity,
18506    })
18507}
18508
18509fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
18510    let expression = arguments.named_child(0)?;
18511    if expression.kind() != "comma_expression" {
18512        return Some(1);
18513    }
18514    let mut arity = 0usize;
18515    let mut stack = vec![expression];
18516    while let Some(node) = stack.pop() {
18517        if node.kind() == "comma_expression" {
18518            stack.push(node.child_by_field_name("right")?);
18519            stack.push(node.child_by_field_name("left")?);
18520        } else {
18521            arity += 1;
18522        }
18523    }
18524    Some(arity)
18525}
18526
18527/// Whether `node` is part of a call's callee expression, walking only through
18528/// the grammar wrappers that can structurally contain that callee.
18529pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
18530    while let Some(parent) = node.parent() {
18531        match parent.kind() {
18532            "call_expression" => {
18533                return parent
18534                    .child_by_field_name("function")
18535                    .or_else(|| parent.named_child(0))
18536                    == Some(node);
18537            }
18538            "qualified_identifier"
18539            | "scoped_identifier"
18540            | "template_function"
18541            | "template_type"
18542            | "field_expression" => node = parent,
18543            _ => return false,
18544        }
18545    }
18546    false
18547}
18548
18549/// Expand a C++ type-name component to the complete written reference that
18550/// owns it. Qualified path wrappers that stop before a later component remain
18551/// separate, so a type used as an owner keeps its prefix range.
18552pub fn type_reference_hit_node(mut node: Node<'_>) -> Node<'_> {
18553    loop {
18554        let Some(parent) = node.parent() else {
18555            return node;
18556        };
18557        if parent.kind() == "template_type"
18558            && parent
18559                .child_by_field_name("name")
18560                .is_some_and(|name| same_node(name, node))
18561        {
18562            node = parent;
18563            continue;
18564        }
18565        if matches!(
18566            parent.kind(),
18567            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
18568        ) && (parent
18569            .child_by_field_name("scope")
18570            .is_some_and(|scope| same_node(scope, node))
18571            || parent
18572                .child_by_field_name("name")
18573                .is_some_and(|name| same_node(name, node)))
18574            && parent.end_byte() == node.end_byte()
18575        {
18576            node = parent;
18577            continue;
18578        }
18579        return node;
18580    }
18581}
18582
18583pub fn normalize_type_text(value: &str) -> String {
18584    strip_tag_type_prefix(
18585        normalize_cpp_whitespace(value)
18586            .trim_start_matches("const ")
18587            .trim_end_matches('*')
18588            .trim_end_matches('&')
18589            .trim(),
18590    )
18591    .to_string()
18592}
18593
18594use crate::declarations::strip_tag_type_prefix;
18595
18596pub fn normalize_reference_name(value: &str) -> Option<String> {
18597    let normalized = normalize_cpp_reference_text(value);
18598    (!normalized.is_empty()).then_some(normalized)
18599}
18600
18601pub fn normalize_cpp_reference_text(value: &str) -> String {
18602    let mut text = normalize_cpp_whitespace(value)
18603        .trim_start_matches("new ")
18604        .trim()
18605        .to_string();
18606    if let Some(index) = text.find(['(', '{']) {
18607        text.truncate(index);
18608    }
18609    if let Some(index) = text.find('<') {
18610        text.truncate(index);
18611    }
18612    let normalized = text
18613        .trim()
18614        .trim_start_matches("const ")
18615        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
18616        .trim_matches(':')
18617        .trim();
18618    strip_tag_type_prefix(normalized).to_string()
18619}
18620
18621pub fn cpp_name_for(unit: &CodeUnit) -> String {
18622    let short = unit.short_name().replace(['.', '$'], "::");
18623    if unit.package_name().is_empty() {
18624        short
18625    } else {
18626        format!("{}::{}", unit.package_name(), short)
18627    }
18628}
18629
18630/// Render an indexed C++ qualified name from its authoritative FqName
18631/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
18632/// that belong to a template argument (for example `Args...`).
18633fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
18634    let fq = unit.fq();
18635    if fq.is_empty() {
18636        return None;
18637    }
18638    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
18639    Some(
18640        fq.segments()
18641            .iter()
18642            .map(|&segment| interner.resolve(segment).0)
18643            .collect::<Vec<_>>()
18644            .join("::"),
18645    )
18646}
18647
18648fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
18649    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
18650        || unit.fq().is_empty() && cpp_name_for(unit) == expected
18651}
18652
18653/// Return the indexed C++ owner scope without reparsing its rendered name.
18654///
18655/// Template spellings are opaque within an indexed `FqName` segment.  In
18656/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
18657/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
18658/// through `parse_symbol_path` would mistake those dots for component
18659/// separators.  Cache-loaded/legacy units may still have an empty structured
18660/// name, so retain the parser only as that explicit fallback.
18661///
18662/// Only a terminal member is not a scope. A callable that encloses a
18663/// function-local class (with a synthetic block owner) is one of that class's
18664/// scope components, exactly as its canonical name spells it.
18665pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
18666    let fq = unit.fq();
18667    if !fq.is_empty() {
18668        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
18669        let segments = fq.segments();
18670        let scope = segments
18671            .iter()
18672            .enumerate()
18673            .filter_map(|(index, &segment)| {
18674                let (text, kind) = interner.resolve(segment);
18675                match kind {
18676                    SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => true,
18677                    SegmentKind::Member => index + 1 < segments.len(),
18678                    SegmentKind::Path | SegmentKind::Companion | SegmentKind::Unknown => false,
18679                }
18680                .then(|| text.to_string())
18681            })
18682            .collect();
18683        return scope;
18684    }
18685    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18686        brokk_bifrost_core::analyzer::Language::Cpp,
18687        &cpp_name_for(unit),
18688    )
18689}
18690
18691// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
18692// (not the substring "->"), which deliberately reduces an `operator->`-style
18693// terminal segment to an empty tail rather than keeping it intact; the shared
18694// structured splitter's cpp operator-token merge would keep `operator->`
18695// whole instead, changing this function's result — `name_matches_callable`'s
18696// `expected.starts_with("operator")` fallback exists specifically to
18697// compensate for that reduction, and a pinned regression test
18698// (`operator-> must not be reduced with terminal_name-style punctuation
18699// splitting`) asserts today's char-class behavior. Not equivalence-provable;
18700// revisit alongside that pinned test if it is ever relaxed.
18701pub fn terminal_name(value: &str) -> &str {
18702    value
18703        .rsplit("::")
18704        .next()
18705        .unwrap_or(value)
18706        .rsplit(['.', '-', '>'])
18707        .next()
18708        .unwrap_or(value)
18709        .trim()
18710}
18711
18712pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
18713    terminal_name(&normalize_cpp_reference_text(value)) == expected
18714}
18715
18716pub fn name_matches_callable(value: &str, expected: &str) -> bool {
18717    name_matches_terminal(value, expected)
18718        || expected.starts_with("operator")
18719            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
18720}
18721
18722pub fn name_mentions(value: &str, expected: &str) -> bool {
18723    normalize_cpp_reference_text(value)
18724        .split("::")
18725        .any(|part| part == expected)
18726}
18727
18728pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
18729    let cpp_name = cpp_name_for(unit);
18730    if reference.contains("::") {
18731        return reference == cpp_name;
18732    }
18733    reference == cpp_name
18734        || terminal_name(reference) == unit.identifier()
18735            && (unit.package_name().is_empty() || reference == unit.identifier())
18736}
18737
18738pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
18739    match kind {
18740        TargetKind::Type
18741        | TargetKind::Constructor
18742        | TargetKind::Method
18743        | TargetKind::MemberField => true,
18744        TargetKind::FreeFunction => unit.is_function(),
18745        TargetKind::GlobalField => unit.is_field(),
18746        TargetKind::Macro => unit.is_macro(),
18747    }
18748}
18749
18750fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
18751    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
18752    let target_name = cpp_name_for(target);
18753    if normalized.contains("::") {
18754        return normalized == target_name;
18755    }
18756    if let Some(namespace) = alias.namespace.as_deref() {
18757        return namespace_prefixes(namespace)
18758            .into_iter()
18759            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
18760    }
18761    target.package_name().is_empty() && normalized == target.identifier()
18762}
18763
18764/// The declared return type text of a C++ function unit, with leading declaration specifiers
18765/// stripped, e.g. `T*` for `T* operator->()`.
18766pub fn cpp_function_return_type_text(
18767    analyzer: &CppGraphSource<'_>,
18768    function: &CodeUnit,
18769) -> Option<String> {
18770    let metadata = analyzer.signature_metadata(function);
18771    let first = metadata.first()?.return_type_text()?;
18772    metadata
18773        .iter()
18774        .all(|metadata| metadata.return_type_text() == Some(first))
18775        .then(|| first.to_owned())
18776}
18777
18778pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
18779    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
18780    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
18781    // the same string `default_parent_fq_name`/`fq().parent()` would render:
18782    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
18783    // `::`) between a trailing `Package` segment and a following `Type`
18784    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
18785    // popping the unit's own `fq()` segment would NOT reproduce this
18786    // fully-`::`-joined string. Left as a split on the locally-built
18787    // all-colon string rather than the unit's structured name.
18788    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
18789        namespace
18790            .strip_prefix("anonymous_namespace::")
18791            .unwrap_or(namespace)
18792            .to_string()
18793    })
18794}
18795
18796/// The namespace lookup tiers, from the innermost namespace outward.
18797pub fn namespace_prefixes(namespace: &str) -> Vec<String> {
18798    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
18799    // non-`::` separator already converted to `::`, so re-tokenizing it with
18800    // the shared structured splitter and progressively popping the last
18801    // component reproduces the `rsplit_once("::")` outward walk exactly (same
18802    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
18803    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18804        brokk_bifrost_core::analyzer::Language::Cpp,
18805        namespace,
18806    );
18807    let mut prefixes = Vec::new();
18808    while !parts.is_empty() {
18809        prefixes.push(parts.join("::"));
18810        parts.pop();
18811    }
18812    prefixes
18813}
18814
18815fn nearest_namespace_candidates(
18816    candidates: Vec<CodeUnit>,
18817    normalized: &str,
18818    lexical_namespace: Option<&str>,
18819) -> Vec<CodeUnit> {
18820    if normalized.contains("::") {
18821        return candidates;
18822    }
18823    if let Some(namespace) = lexical_namespace {
18824        for prefix in namespace_prefixes(namespace) {
18825            let scoped = candidates
18826                .iter()
18827                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
18828                .cloned()
18829                .collect::<Vec<_>>();
18830            if !scoped.is_empty() {
18831                return scoped;
18832            }
18833        }
18834    }
18835    candidates
18836        .into_iter()
18837        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
18838        .collect()
18839}
18840
18841pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
18842    let mut namespaces = Vec::new();
18843    for parent in enclosing_cpp_namespace_nodes(node) {
18844        if let Some(name) = parent.child_by_field_name("name") {
18845            let namespace = normalize_cpp_reference_text(node_text(name, source));
18846            if !namespace.is_empty() {
18847                namespaces.push(namespace);
18848            }
18849        }
18850    }
18851    if namespaces.is_empty() {
18852        None
18853    } else {
18854        Some(namespaces.join("::"))
18855    }
18856}
18857
18858/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
18859/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
18860/// globals rather than members.
18861pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18862    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
18863}
18864
18865fn type_owner_resolution(
18866    analyzer: &CppGraphSource<'_>,
18867    code_unit: &CodeUnit,
18868) -> Option<ResolvedTypeOwner> {
18869    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
18870}
18871
18872fn target_type_owner_resolution(
18873    analyzer: &CppGraphSource<'_>,
18874    code_unit: &CodeUnit,
18875) -> Option<ResolvedTypeOwner> {
18876    match type_owner_resolution(analyzer, code_unit) {
18877        Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
18878        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
18879    }
18880}
18881
18882/// Recover method identity for an indexed out-of-line definition when the
18883/// ordinary parent edge is absent. Prefer the unique include-visible forward
18884/// declaration, then classify exact-FQN class declarations elsewhere in the
18885/// workspace. A unique complete declaration wins; otherwise multiple forward
18886/// declarations are one owner only when they all share one logical identity.
18887/// The qualified callable FQN proves that owner spelling even when its defining
18888/// header is outside the scan file's include closure, while unknown or competing
18889/// complete declarations remain ambiguous.
18890/// This is deliberately target-only: canonical declaration resolution must
18891/// continue to prefer the callable definition rather than replacing it with
18892/// the recovered owner.
18893fn target_forward_owner_resolution(
18894    analyzer: &CppGraphSource<'_>,
18895    code_unit: &CodeUnit,
18896) -> Option<ResolvedTypeOwner> {
18897    if !code_unit.is_function() {
18898        return None;
18899    }
18900    // A top-level free function has no owner at all, and `FqName::parent`
18901    // answers the empty name rather than `None` for a one-segment identity.
18902    // `default_parent_fq_name`, which this replaced, filtered that case out;
18903    // asking the relational store for the empty name is a batch error that
18904    // fails the whole target frontier.
18905    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
18906    let cpp = analyzer.cpp?;
18907    let mut visible_files = HashSet::default();
18908    collect_include_closure(
18909        analyzer,
18910        &cpp.include_target_index(),
18911        code_unit.source(),
18912        &mut visible_files,
18913        None,
18914    );
18915    let candidates = analyzer.workspace_definitions().exact(&owner_name);
18916    let visible_candidates = candidates
18917        .iter()
18918        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18919        .cloned()
18920        .collect::<Vec<_>>();
18921    match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
18922        DirectOwnerResolution::UniqueFull(unit) => {
18923            return Some(ResolvedTypeOwner {
18924                unit,
18925                is_forward_declaration: false,
18926            });
18927        }
18928        DirectOwnerResolution::ForwardsOnly(forwards) => {
18929            return (forwards.len() == 1).then(|| ResolvedTypeOwner {
18930                unit: forwards.into_iter().next().unwrap(),
18931                is_forward_declaration: true,
18932            });
18933        }
18934        DirectOwnerResolution::Ambiguous => return None,
18935        DirectOwnerResolution::None => {}
18936    }
18937
18938    let candidates = candidates
18939        .into_iter()
18940        .filter(|candidate| candidate.is_class())
18941        .collect::<Vec<_>>();
18942    let (unit, is_forward_declaration) =
18943        match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
18944            DirectOwnerResolution::UniqueFull(unit) => (unit, false),
18945            DirectOwnerResolution::ForwardsOnly(forwards) => {
18946                (unique_logical_forward_owner(forwards)?, true)
18947            }
18948            DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
18949        };
18950    Some(ResolvedTypeOwner {
18951        unit,
18952        is_forward_declaration,
18953    })
18954}
18955
18956pub fn precise_parent_of(
18957    analyzer: &CppGraphSource<'_>,
18958    visibility: &VisibilityIndex<'_>,
18959    code_unit: &CodeUnit,
18960) -> Option<CodeUnit> {
18961    visibility.cached_precise_parent_of(analyzer, code_unit)
18962}
18963
18964fn precise_parent_resolution(
18965    analyzer: &CppGraphSource<'_>,
18966    code_unit: &CodeUnit,
18967) -> Option<ResolvedTypeOwner> {
18968    #[cfg(any(test, feature = "test-support"))]
18969    if let Some(cpp) = analyzer.cpp {
18970        cpp.record_cpp_parent_resolution_for_test();
18971    }
18972    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
18973        return Some(ResolvedTypeOwner {
18974            unit,
18975            is_forward_declaration: false,
18976        });
18977    }
18978    let fallback = analyzer.parent_of(code_unit);
18979    if !code_unit.owner_is_type_scope() {
18980        return fallback.map(|unit| ResolvedTypeOwner {
18981            unit,
18982            is_forward_declaration: false,
18983        });
18984    }
18985    let owner_fq = code_unit
18986        .fq()
18987        .parent()
18988        .expect("a unit with an owner identifier has a structured parent");
18989    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
18990    match same_source_owner(analyzer, code_unit, &owner_candidates) {
18991        DirectOwnerResolution::UniqueFull(owner) => {
18992            return Some(ResolvedTypeOwner {
18993                unit: owner,
18994                is_forward_declaration: false,
18995            });
18996        }
18997        DirectOwnerResolution::Ambiguous => return None,
18998        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
18999    }
19000    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
19001        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
19002            unit: owner,
19003            is_forward_declaration: false,
19004        }),
19005        DirectOwnerResolution::Ambiguous => None,
19006        DirectOwnerResolution::ForwardsOnly(forwards) => {
19007            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
19008                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
19009                    unit: owner,
19010                    is_forward_declaration: false,
19011                }),
19012                FullOwnerResolution::None => {
19013                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
19014                        unit,
19015                        is_forward_declaration: true,
19016                    })
19017                }
19018                FullOwnerResolution::Ambiguous => None,
19019            }
19020        }
19021        DirectOwnerResolution::None => {
19022            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
19023                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
19024                    unit: owner,
19025                    is_forward_declaration: false,
19026                }),
19027                FullOwnerResolution::Ambiguous => None,
19028                FullOwnerResolution::None => fallback
19029                    .filter(|parent| {
19030                        parent.source() == code_unit.source()
19031                            && parent.fq() == &owner_fq
19032                            && (!parent.is_class()
19033                                || cpp_class_declaration_strength(analyzer, parent)
19034                                    == CppClassDeclarationStrength::Full)
19035                    })
19036                    .map(|unit| ResolvedTypeOwner {
19037                        unit,
19038                        is_forward_declaration: false,
19039                    }),
19040            }
19041        }
19042    }
19043}
19044
19045fn exact_structural_type_parent(
19046    analyzer: &CppGraphSource<'_>,
19047    code_unit: &CodeUnit,
19048) -> Option<CodeUnit> {
19049    if !code_unit.is_function() && !code_unit.is_field() {
19050        return None;
19051    }
19052    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
19053    let cpp = analyzer.cpp?;
19054    let parent = cpp.structural_parent_of(code_unit)?;
19055    (!parent.is_module()
19056        && parent.source() == code_unit.source()
19057        && parent.package_name() == code_unit.package_name()
19058        && parent.short_name() == encoded_owner)
19059        .then_some(parent)
19060}
19061
19062fn same_source_owner(
19063    analyzer: &CppGraphSource<'_>,
19064    code_unit: &CodeUnit,
19065    owner_candidates: &[CodeUnit],
19066) -> DirectOwnerResolution {
19067    let candidates = owner_candidates
19068        .iter()
19069        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
19070        .cloned()
19071        .collect::<Vec<_>>();
19072    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
19073    classify_direct_owner_candidates(analyzer, candidates.into_iter())
19074}
19075
19076fn visible_full_cpp_owner(
19077    analyzer: &CppGraphSource<'_>,
19078    code_unit: &CodeUnit,
19079    owner_candidates: &[CodeUnit],
19080) -> FullOwnerResolution {
19081    let Some(cpp) = analyzer.cpp else {
19082        return FullOwnerResolution::None;
19083    };
19084    let mut visible_files = HashSet::default();
19085    collect_include_closure(
19086        analyzer,
19087        &cpp.include_target_index(),
19088        code_unit.source(),
19089        &mut visible_files,
19090        None,
19091    );
19092    let candidates = owner_candidates
19093        .iter()
19094        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
19095        .cloned()
19096        .collect::<Vec<_>>();
19097    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
19098    match collapse_owner_candidates(candidates.into_iter().map(|candidate| {
19099        let strength = cpp_class_declaration_strength(analyzer, &candidate);
19100        (candidate, strength)
19101    })) {
19102        DirectOwnerResolution::UniqueFull(owner) => FullOwnerResolution::Unique(owner),
19103        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {
19104            FullOwnerResolution::None
19105        }
19106        DirectOwnerResolution::Ambiguous => FullOwnerResolution::Ambiguous,
19107    }
19108}
19109
19110pub enum DirectOwnerResolution {
19111    None,
19112    ForwardsOnly(Vec<CodeUnit>),
19113    UniqueFull(CodeUnit),
19114    Ambiguous,
19115}
19116
19117enum FullOwnerResolution {
19118    None,
19119    Unique(CodeUnit),
19120    Ambiguous,
19121}
19122
19123fn directly_included_owner(
19124    analyzer: &CppGraphSource<'_>,
19125    code_unit: &CodeUnit,
19126    owner_candidates: &[CodeUnit],
19127) -> DirectOwnerResolution {
19128    let Some(cpp) = analyzer.cpp else {
19129        return DirectOwnerResolution::None;
19130    };
19131    let direct_includes: HashSet<ProjectFile> = analyzer
19132        .canonical_include_paths(code_unit.source())
19133        .unwrap_or_default()
19134        .into_iter()
19135        .flat_map(|include| {
19136            resolve_include_targets_with_index(
19137                code_unit.source(),
19138                &include,
19139                &cpp.include_target_index(),
19140            )
19141        })
19142        .collect();
19143    let candidates = owner_candidates
19144        .iter()
19145        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
19146        .cloned()
19147        .collect::<Vec<_>>();
19148    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
19149    classify_direct_owner_candidates(analyzer, candidates.into_iter())
19150}
19151
19152fn prefer_member_declaring_owners(
19153    analyzer: &CppGraphSource<'_>,
19154    member: &CodeUnit,
19155    candidates: Vec<CodeUnit>,
19156) -> Vec<CodeUnit> {
19157    let matching = candidates
19158        .iter()
19159        .filter(|owner| owner_declares_member(analyzer, owner, member))
19160        .cloned()
19161        .collect::<Vec<_>>();
19162    if matching.is_empty() {
19163        candidates
19164    } else {
19165        matching
19166    }
19167}
19168
19169fn owner_declares_member(
19170    analyzer: &CppGraphSource<'_>,
19171    owner: &CodeUnit,
19172    member: &CodeUnit,
19173) -> bool {
19174    analyzer.direct_children(owner).into_iter().any(|child| {
19175        child.kind() == member.kind()
19176            && child.identifier() == member.identifier()
19177            && child.signature() == member.signature()
19178    })
19179}
19180
19181fn classify_direct_owner_candidates(
19182    analyzer: &CppGraphSource<'_>,
19183    candidates: impl Iterator<Item = CodeUnit>,
19184) -> DirectOwnerResolution {
19185    collapse_owner_candidates(candidates.map(|candidate| {
19186        let strength = cpp_class_declaration_strength(analyzer, &candidate);
19187        (candidate, strength)
19188    }))
19189}
19190
19191pub fn collapse_owner_candidates(
19192    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
19193) -> DirectOwnerResolution {
19194    let mut full_definition = None;
19195    let mut forwards = Vec::new();
19196    for (candidate, strength) in candidates {
19197        match strength {
19198            CppClassDeclarationStrength::Full if full_definition.is_some() => {
19199                return DirectOwnerResolution::Ambiguous;
19200            }
19201            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
19202            CppClassDeclarationStrength::Forward => forwards.push(candidate),
19203            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
19204        }
19205    }
19206    if let Some(owner) = full_definition {
19207        DirectOwnerResolution::UniqueFull(owner)
19208    } else if !forwards.is_empty() {
19209        DirectOwnerResolution::ForwardsOnly(forwards)
19210    } else {
19211        DirectOwnerResolution::None
19212    }
19213}
19214
19215#[cfg(any(test, feature = "test-support"))]
19216pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
19217    unique_logical_forward_owner(forwards)
19218}
19219
19220fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
19221    let first = forwards.pop()?;
19222    forwards
19223        .iter()
19224        .all(|forward| same_logical_symbol(forward, &first))
19225        .then_some(first)
19226}
19227
19228pub fn cpp_class_declaration_strength(
19229    analyzer: &CppGraphSource<'_>,
19230    candidate: &CodeUnit,
19231) -> CppClassDeclarationStrength {
19232    // The answer is a pure function of the unit's ranges and its file's tree,
19233    // and the inverse scan asks it once per declaration seed. On a translation
19234    // unit the parser could not fully recover, each ask re-derives the
19235    // export-macro recovery shapes from the file's `ERROR` subtrees, so without
19236    // this memo one file's scan is quadratic in its own size: 97% of Catch2's
19237    // 284 s inverse scan of `extras/catch_amalgamated.cpp` was in this call
19238    // (#1496).
19239    let Some(cpp) = analyzer.cpp else {
19240        return uncached_cpp_class_declaration_strength(analyzer, candidate);
19241    };
19242    if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
19243        return strength;
19244    }
19245    let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
19246    cpp.cache_class_declaration_strength(candidate, strength);
19247    strength
19248}
19249
19250fn uncached_cpp_class_declaration_strength(
19251    analyzer: &CppGraphSource<'_>,
19252    candidate: &CodeUnit,
19253) -> CppClassDeclarationStrength {
19254    if let Some(facts) = analyzer.declaration_source_properties(candidate) {
19255        if facts
19256            .iter()
19257            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
19258        {
19259            return CppClassDeclarationStrength::Full;
19260        }
19261        if facts
19262            .iter()
19263            .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
19264        {
19265            return CppClassDeclarationStrength::Forward;
19266        }
19267    }
19268    if let Some(cpp) = analyzer.cpp
19269        && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
19270    {
19271        return cpp_class_declaration_strength_in_tree(
19272            analyzer,
19273            &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
19274            candidate,
19275            prepared.source(),
19276            prepared.tree().root_node(),
19277        );
19278    }
19279    let Some(source) = analyzer.indexed_source(candidate.source()) else {
19280        return CppClassDeclarationStrength::Unknown;
19281    };
19282    let mut parser = Parser::new();
19283    if parser
19284        .set_language(&tree_sitter_cpp::LANGUAGE.into())
19285        .is_err()
19286    {
19287        return CppClassDeclarationStrength::Unknown;
19288    }
19289    let Some(tree) = parser.parse(&source, None) else {
19290        return CppClassDeclarationStrength::Unknown;
19291    };
19292    // This branch reparses a file the analyzer has no prepared tree for, so its
19293    // recovery index is that one tree's and cannot be shared.
19294    let recovered_export_classes =
19295        CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
19296    cpp_class_declaration_strength_in_tree(
19297        analyzer,
19298        &recovered_export_classes,
19299        candidate,
19300        &source,
19301        tree.root_node(),
19302    )
19303}
19304
19305fn cpp_class_declaration_strength_in_tree(
19306    analyzer: &CppGraphSource<'_>,
19307    recovered_export_classes: &CppRecoveredExportClassIndex,
19308    candidate: &CodeUnit,
19309    source: &str,
19310    root: Node<'_>,
19311) -> CppClassDeclarationStrength {
19312    let ranges = analyzer.ranges(candidate);
19313    let mut saw_forward = false;
19314    for range in ranges {
19315        // The recovered export-macro shapes answer for their own ranges; only a
19316        // range no recovery claims is read as a plain specifier.
19317        match recovered_class_body_at(
19318            recovered_export_classes,
19319            root,
19320            source,
19321            candidate.identifier(),
19322            &range,
19323        ) {
19324            Some(true) => return CppClassDeclarationStrength::Full,
19325            Some(false) => {
19326                saw_forward = true;
19327                continue;
19328            }
19329            None => {}
19330        }
19331        // Only a node covering the range's start byte can be the specifier for
19332        // this range, so apply that test where nodes enter the stack rather
19333        // than where they leave it. Pushing first meant one ask enqueued every
19334        // sibling at every level it descended, which on a translation unit with
19335        // thousands of top-level declarations is a per-ask cost proportional to
19336        // the file (#1496).
19337        let covers_range_start = |node: &Node<'_>| {
19338            node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
19339        };
19340        let mut stack = Vec::new();
19341        if covers_range_start(&root) {
19342            stack.push(root);
19343        }
19344        while let Some(node) = stack.pop() {
19345            if node.start_byte() == range.start_byte
19346                && node.end_byte() == range.end_byte
19347                && matches!(
19348                    node.kind(),
19349                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
19350                )
19351            {
19352                if cpp_class_node_has_body(node) {
19353                    return CppClassDeclarationStrength::Full;
19354                }
19355                saw_forward = true;
19356            }
19357            let mut cursor = node.walk();
19358            stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
19359        }
19360    }
19361    if saw_forward {
19362        CppClassDeclarationStrength::Forward
19363    } else {
19364        CppClassDeclarationStrength::Unknown
19365    }
19366}
19367
19368fn cpp_class_node_has_body(node: Node<'_>) -> bool {
19369    child_by_field_or_named_child_of_kinds(
19370        node,
19371        "body",
19372        &[
19373            "declaration_list",
19374            "field_declaration_list",
19375            "enumerator_list",
19376        ],
19377    )
19378    .is_some()
19379}
19380
19381#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19382enum CppCTagKind {
19383    Struct,
19384    Union,
19385}
19386
19387fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
19388    let declaration = analyzer.get_source(code_unit, false)?;
19389    let mut parser = Parser::new();
19390    parser
19391        .set_language(&tree_sitter_cpp::LANGUAGE.into())
19392        .ok()?;
19393    let tree = parser.parse(&declaration, None)?;
19394    let mut stack = vec![tree.root_node()];
19395    while let Some(node) = stack.pop() {
19396        let kind = match node.kind() {
19397            "struct_specifier" => CppCTagKind::Struct,
19398            "union_specifier" => CppCTagKind::Union,
19399            _ => {
19400                let mut cursor = node.walk();
19401                stack.extend(node.named_children(&mut cursor));
19402                continue;
19403            }
19404        };
19405        if node
19406            .child_by_field_name("name")
19407            .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
19408        {
19409            return Some(kind);
19410        }
19411        let mut cursor = node.walk();
19412        stack.extend(node.named_children(&mut cursor));
19413    }
19414    None
19415}
19416
19417pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
19418    if !code_unit.owner_is_type_scope() {
19419        return None;
19420    }
19421    let owner_fq = code_unit.fq().parent()?;
19422    ctx.analyzer
19423        .workspace_definitions()
19424        .exact(&owner_fq)
19425        .into_iter()
19426        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
19427}
19428
19429pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19430    same_logical_symbol(left, right) && left.source() == right.source()
19431}
19432
19433pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19434    same_symbol(left, right) || same_logical_symbol(left, right)
19435}
19436
19437pub fn same_visible_global_field_symbol(
19438    analyzer: &CppGraphSource<'_>,
19439    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
19440    left: &CodeUnit,
19441    right: &CodeUnit,
19442) -> bool {
19443    if same_symbol(left, right) {
19444        return true;
19445    }
19446    if !same_logical_symbol(left, right) {
19447        return false;
19448    }
19449    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
19450        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
19451    {
19452        left.source() == right.source()
19453    } else {
19454        true
19455    }
19456}
19457
19458fn cpp_global_field_has_internal_linkage_cached(
19459    analyzer: &CppGraphSource<'_>,
19460    cache: &mut HashMap<CodeUnit, bool>,
19461    candidate: &CodeUnit,
19462) -> bool {
19463    if let Some(internal) = cache.get(candidate) {
19464        return *internal;
19465    }
19466    #[cfg(any(test, feature = "test-support"))]
19467    note_cpp_global_field_internal_linkage_classification_for_test();
19468    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
19469    cache.insert(candidate.clone(), internal);
19470    internal
19471}
19472
19473#[cfg(any(test, feature = "test-support"))]
19474thread_local! {
19475    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
19476}
19477
19478#[cfg(any(test, feature = "test-support"))]
19479fn note_cpp_global_field_internal_linkage_classification_for_test() {
19480    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
19481        count.set(count.get() + 1);
19482    });
19483}
19484
19485#[cfg(any(test, feature = "test-support"))]
19486pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
19487    body: impl FnOnce() -> T,
19488) -> (T, usize) {
19489    with_thread_local_counter_for_test(
19490        &CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST,
19491        body,
19492    )
19493}
19494
19495#[cfg(any(test, feature = "test-support"))]
19496fn with_thread_local_counter_for_test<T>(
19497    counter: &'static std::thread::LocalKey<Cell<usize>>,
19498    body: impl FnOnce() -> T,
19499) -> (T, usize) {
19500    counter.with(|count| {
19501        count.set(0);
19502        let result = body();
19503        let observed = count.get();
19504        count.set(0);
19505        (result, observed)
19506    })
19507}
19508
19509pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19510    left.kind() == right.kind()
19511        && left.fq_name() == right.fq_name()
19512        && left.signature() == right.signature()
19513}
19514
19515/// Select one visible spelling from an alias family only when its members
19516/// agree on one structured target. A single candidate needs no target proof.
19517pub fn select_alias_spelling_with_agreed_targets<'a, T: Eq>(
19518    candidates: &[&'a CodeUnit],
19519    mut structured_target: impl FnMut(&CodeUnit) -> Option<T>,
19520) -> Option<&'a CodeUnit> {
19521    let first = *candidates.first()?;
19522    if candidates.len() == 1 {
19523        return Some(first);
19524    }
19525    let first_target = structured_target(first)?;
19526    candidates
19527        .iter()
19528        .skip(1)
19529        .all(|candidate| structured_target(candidate).is_some_and(|target| target == first_target))
19530        .then_some(first)
19531}
19532
19533pub fn cpp_global_field_has_internal_linkage(
19534    analyzer: &CppGraphSource<'_>,
19535    candidate: &CodeUnit,
19536) -> bool {
19537    if !candidate.is_field() || candidate.short_name().contains('.') {
19538        return false;
19539    }
19540    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
19541        return false;
19542    };
19543    match local_linkage {
19544        CppFieldLinkage::Internal => true,
19545        CppFieldLinkage::External => false,
19546        CppFieldLinkage::InternalUnlessExternalPeer => {
19547            !cpp_global_field_linkage_peers(analyzer, candidate)
19548                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
19549                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
19550        }
19551    }
19552}
19553
19554fn cpp_global_field_linkage_peers<'a>(
19555    analyzer: &CppGraphSource<'a>,
19556    candidate: &'a CodeUnit,
19557) -> impl Iterator<Item = CodeUnit> + 'a {
19558    let name = candidate.fq().clone();
19559    analyzer
19560        .workspace_definitions()
19561        .exact(&name)
19562        .into_iter()
19563        .filter(move |peer| {
19564            if peer == candidate {
19565                return false;
19566            }
19567            #[cfg(any(test, feature = "test-support"))]
19568            note_cpp_global_field_linkage_peer_inspection_for_test();
19569            same_logical_symbol(peer, candidate)
19570        })
19571}
19572
19573#[cfg(any(test, feature = "test-support"))]
19574thread_local! {
19575    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
19576}
19577
19578#[cfg(any(test, feature = "test-support"))]
19579fn note_cpp_global_field_linkage_peer_inspection_for_test() {
19580    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
19581        count.set(count.get() + 1);
19582    });
19583}
19584
19585#[cfg(any(test, feature = "test-support"))]
19586pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
19587    body: impl FnOnce() -> T,
19588) -> (T, usize) {
19589    with_thread_local_counter_for_test(&CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST, body)
19590}
19591
19592fn cpp_global_field_declaration_linkage(
19593    analyzer: &CppGraphSource<'_>,
19594    candidate: &CodeUnit,
19595) -> Option<CppFieldLinkage> {
19596    analyzer.cpp_field_linkage(candidate)
19597}
19598
19599/// Agreed alias target spelling captured from the declaring AST type field.
19600pub fn cpp_canonical_alias_target_text(
19601    analyzer: &CppGraphSource<'_>,
19602    unit: &CodeUnit,
19603) -> Option<String> {
19604    let facts = analyzer.declaration_source_properties(unit)?;
19605    let first_fact = facts.first()?;
19606    let first = first_fact.alias_target_text.as_ref()?;
19607    let normalized = normalize_cpp_whitespace(first);
19608    // Alias signatures collapse whitespace, so repeated declarations of one
19609    // unit can retain different raw spellings. Require structural agreement
19610    // too: whitespace inside a literal can change a type expression's value.
19611    facts
19612        .iter()
19613        .all(|fact| {
19614            fact.alias_target_text.as_ref().is_some_and(|target| {
19615                target == first
19616                    || (first_fact.alias_target.is_some()
19617                        && fact.alias_target == first_fact.alias_target
19618                        && normalize_cpp_whitespace(target) == normalized)
19619            })
19620        })
19621        .then(|| first.clone())
19622}
19623
19624#[cfg(test)]
19625mod tests {
19626
19627    #[test]
19628    fn resolver_displaced_conditional_grammar_boundary() {
19629        // With FLAG defined this is an ordinary array declaration. The
19630        // unpreprocessed CST swallows the directive into its array bound and
19631        // incorrectly keeps the following declaration under the conditional.
19632        let source = "#ifdef FLAG\nint values[\n#endif\n1];\nint outside;\n";
19633        let tree = parse_cpp(source);
19634        let conditional = tree.root_node().named_child(0).unwrap();
19635        assert_eq!(conditional.kind(), "preproc_ifdef");
19636        assert!(conditional.has_error());
19637        let boundary = cpp_displaced_preprocessor_boundary(conditional).unwrap();
19638        let directive = crate::grammar_test_support::nodes(conditional)
19639            .into_iter()
19640            .find(|node| node.kind() == "#endif" && !node.is_missing())
19641            .unwrap();
19642        assert_eq!(directive.parent().unwrap().kind(), "ERROR");
19643        assert_eq!(directive.end_byte(), boundary.end_byte);
19644        let inside = named_node_at(&tree, source, "values");
19645        let outside = named_node_at(&tree, source, "outside");
19646        assert!(has_ancestor_kind(inside, "preproc_ifdef"));
19647        assert!(has_ancestor_kind(outside, "preproc_ifdef"));
19648        assert!(inside.end_byte() < directive.start_byte());
19649        assert!(outside.start_byte() > directive.end_byte());
19650        assert!(preprocessor_conditional_contains_descendant(
19651            conditional,
19652            inside
19653        ));
19654        assert!(!preprocessor_conditional_contains_descendant(
19655            conditional,
19656            outside
19657        ));
19658    }
19659
19660    #[test]
19661    fn resolver_recovered_namespace_boundaries() {
19662        let source = STOLEN_BRACE_CASCADE;
19663        let tree = parse_cpp(source);
19664        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19665        assert!(!index.regions.is_empty(), "fixture must enter recovery");
19666        for (needle, expected) in [
19667            ("First", vec!["app", "matchers", "detail"]),
19668            ("Second", vec!["app", "matchers", "detail"]),
19669            ("tail_function", vec!["app", "matchers"]),
19670            ("TailClass", vec!["app", "matchers"]),
19671            ("AfterAll", vec![]),
19672        ] {
19673            let node = named_node_at(&tree, source, needle);
19674            let mut parsed: Vec<_> = std::iter::successors(node.parent(), |node| node.parent())
19675                .filter(|node| node.kind() == "namespace_definition")
19676                .filter_map(|namespace| {
19677                    let name = namespace.child_by_field_name("name")?;
19678                    let mut components = Vec::new();
19679                    append_cpp_name_components(name, source, &mut components)?;
19680                    Some((
19681                        namespace.start_byte(),
19682                        components,
19683                        namespace_definition_has_real_braces(namespace),
19684                    ))
19685                })
19686                .collect();
19687            parsed.reverse();
19688            assert_eq!(
19689                index.restore_enclosing_namespaces(parsed, node.start_byte()),
19690                expected,
19691                "{needle}"
19692            );
19693        }
19694        // A range owns its bytes, not the first byte following the range.
19695        for region in &index.regions {
19696            assert!(region.start < region.end);
19697            assert!(index.region_at(region.start).is_some());
19698            assert!(
19699                index
19700                    .region_at(region.end)
19701                    .is_none_or(|found| !std::ptr::eq(found, region)),
19702                "region leaked beyond its half-open boundary: {region:?}"
19703            );
19704        }
19705        // Real recovery ERRORs can carry a close and additional tokens. The
19706        // close still terminates lexical scope; it need not be the only token.
19707        let errors: Vec<_> = crate::grammar_test_support::nodes(tree.root_node())
19708            .into_iter()
19709            .filter(|node| node.kind() == "ERROR")
19710            .filter(|node| {
19711                let mut cursor = node.walk();
19712                node.children(&mut cursor).any(|child| child.kind() == "}")
19713            })
19714            .collect();
19715        assert!(
19716            !errors.is_empty(),
19717            "fixture must retain unmatched close tokens"
19718        );
19719        for error in errors {
19720            assert!(direct_unmatched_closing_brace(error), "{error:?}");
19721        }
19722    }
19723
19724    // These tables pin the fields and recovery tokens supplied by the released
19725    // grammar, alongside the role that the resolver reads at that boundary.
19726    #[test]
19727    fn resolver_declarator_grammar_boundary_table() {
19728        for (source, name, kind, binder) in [
19729            ("int first, values[3];", "values", "identifier", true),
19730            (
19731                "void f() { int first, values[3]; }",
19732                "values",
19733                "identifier",
19734                true,
19735            ),
19736            (
19737                "typedef unsigned Underlying; enum Named : Underlying { Value };",
19738                "Underlying {",
19739                "type_identifier",
19740                false,
19741            ),
19742        ] {
19743            let tree = parse_cpp(source);
19744            assert!(!tree.root_node().has_error(), "{source}");
19745            let spelling = name.split_whitespace().next().unwrap();
19746            let start = source.find(name).unwrap();
19747            let node = tree
19748                .root_node()
19749                .named_descendant_for_byte_range(start, start + spelling.len())
19750                .unwrap();
19751            assert_eq!(node.kind(), kind, "{source}");
19752            if spelling == "values" {
19753                let array = node.parent().unwrap();
19754                assert_eq!(array.kind(), "array_declarator");
19755                assert_eq!(array.child_by_field_name("declarator"), Some(node));
19756                assert!(is_declarator_node(array), "{source}");
19757            } else {
19758                let enumeration = node.parent().unwrap();
19759                assert_eq!(enumeration.kind(), "enum_specifier");
19760                assert_eq!(enumeration.child_by_field_name("base"), Some(node));
19761                assert_eq!(
19762                    enumeration.child_by_field_name("name").is_some(),
19763                    source.contains("enum Named")
19764                );
19765            }
19766            assert_eq!(is_declaration_name(node), binder, "{source}");
19767        }
19768    }
19769
19770    #[test]
19771    fn resolver_alias_scope_and_argument_grammar_table() {
19772        for (source, scope) in [
19773            ("template<class T> using Alias = T;", true),
19774            ("namespace n { template<class T> using Alias = T; }", true),
19775            (
19776                "struct Owner { template<class T> using Alias = T; };",
19777                false,
19778            ),
19779            ("void f() { using Alias = int; }", false),
19780        ] {
19781            let tree = parse_cpp(source);
19782            assert!(!tree.root_node().has_error(), "{source}");
19783            let alias = crate::grammar_test_support::nodes(tree.root_node())
19784                .into_iter()
19785                .find(|node| node.kind() == "alias_declaration")
19786                .unwrap();
19787            assert_eq!(
19788                node_text(alias.child_by_field_name("name").unwrap(), source),
19789                "Alias"
19790            );
19791            assert!(alias.child_by_field_name("type").is_some());
19792            assert_eq!(alias_has_visible_file_scope(alias), scope, "{source}");
19793        }
19794        for (source, argument_kinds, expression) in [
19795            (
19796                "ns::Alias value(42, arg);",
19797                vec!["number_literal", "identifier"],
19798                true,
19799            ),
19800            (
19801                "ns::Alias value(42, 7);",
19802                vec!["number_literal", "number_literal"],
19803                false,
19804            ),
19805            ("ns::Alias value();", vec![], false),
19806        ] {
19807            let tree = parse_cpp(source);
19808            assert!(!tree.root_node().has_error(), "{source}");
19809            let declaration = tree.root_node().named_child(0).unwrap();
19810            let ty = declaration.child_by_field_name("type").unwrap();
19811            assert_eq!(node_text(ty, source), "ns::Alias");
19812            let argument_lists: Vec<_> = crate::grammar_test_support::nodes(declaration)
19813                .into_iter()
19814                .filter(|node| node.kind() == "argument_list")
19815                .collect();
19816            let kinds: Vec<_> = argument_lists
19817                .iter()
19818                .flat_map(|node| {
19819                    let mut cursor = node.walk();
19820                    node.named_children(&mut cursor)
19821                        .map(|child| child.kind())
19822                        .collect::<Vec<_>>()
19823                })
19824                .collect();
19825            assert_eq!(kinds, argument_kinds, "{source}");
19826            assert_eq!(
19827                qualified_alias_constructor_has_expression_argument(ty),
19828                expression,
19829                "{source}"
19830            );
19831        }
19832    }
19833
19834    #[test]
19835    fn resolver_namespace_brace_grammar_table() {
19836        for (source, complete, closing_missing) in [
19837            ("namespace n { int item; }", true, false),
19838            ("namespace n { int item;", false, true),
19839        ] {
19840            let tree = parse_cpp(source);
19841            let namespace = tree.root_node().named_child(0).unwrap();
19842            assert_eq!(namespace.kind(), "namespace_definition");
19843            let body = namespace.child_by_field_name("body").unwrap();
19844            assert_eq!(body.kind(), "declaration_list");
19845            let open = body.child(0).unwrap();
19846            let close = body.child(body.child_count() - 1).unwrap();
19847            assert_eq!(open.kind(), "{");
19848            assert!(!open.is_missing());
19849            assert_eq!(close.kind(), "}");
19850            assert_eq!(close.is_missing(), closing_missing);
19851            assert_eq!(
19852                namespace_definition_has_real_braces(namespace),
19853                complete,
19854                "{source}"
19855            );
19856        }
19857        let source = "namespace { namespace detail { struct Derived {}; void run() {} } }";
19858        let tree = parse_cpp(source);
19859        assert!(!tree.root_node().has_error());
19860        let anonymous = tree.root_node().named_child(0).unwrap();
19861        assert_eq!(anonymous.kind(), "namespace_definition");
19862        assert!(anonymous.child_by_field_name("name").is_none());
19863        let run = named_node_at(&tree, source, "run");
19864        assert_eq!(
19865            enclosing_namespace_components(run, source),
19866            Some(vec!["detail".to_owned()])
19867        );
19868    }
19869
19870    #[test]
19871    fn resolver_c_block_envelope_grammar_table() {
19872        for (source, name, damaged, recovered) in [
19873            ("void outer(void) { nested() {} }", "nested", false, false),
19874            (
19875                "void outer(void) { Apply(first, ^(Bucket b) { return b.value; }); }",
19876                "Apply",
19877                true,
19878                true,
19879            ),
19880        ] {
19881            let tree = parse_cpp(source);
19882            let node = named_node_at(&tree, source, name);
19883            let declarator = node.parent().unwrap();
19884            assert_eq!(
19885                declarator.kind(),
19886                "function_declarator",
19887                "{source}: {:?}",
19888                tree.root_node()
19889            );
19890            assert_eq!(declarator.child_by_field_name("declarator"), Some(node));
19891            assert_eq!(declarator.has_error(), damaged, "{source}");
19892            let definition = declarator.parent().unwrap();
19893            assert_eq!(definition.kind(), "function_definition");
19894            assert!(definition.child_by_field_name("type").is_none());
19895            assert!(definition.child_by_field_name("body").is_some());
19896            assert_eq!(
19897                recovered_c_block_call_envelope(declarator).is_some(),
19898                recovered,
19899                "{source}"
19900            );
19901        }
19902    }
19903
19904    #[test]
19905    fn resolver_linkage_guard_grammar_table() {
19906        for (source, recovered, visible) in [
19907            (
19908                "#ifdef __cplusplus\nextern \"C\" {\n#endif\nvoid target();\n#ifdef __cplusplus\n}\n#endif\n",
19909                false,
19910                true,
19911            ),
19912            (
19913                "#ifdef __cplusplus\nextern \"C\" {\nvoid target();\n#ifdef __cplusplus\n}\n#endif\n",
19914                true,
19915                false,
19916            ),
19917            (
19918                "#ifdef __cplusplus\nextern \"C\" {\nvoid target();\n}\n#endif\n",
19919                true,
19920                false,
19921            ),
19922        ] {
19923            let tree = parse_cpp(source);
19924            let name = named_node_at(&tree, source, "target");
19925            let mut declaration = name;
19926            while declaration.kind() != "declaration" {
19927                declaration = declaration.parent().unwrap();
19928            }
19929            assert_eq!(
19930                has_ancestor_kind(declaration, "linkage_specification"),
19931                recovered,
19932                "{source}"
19933            );
19934            assert_eq!(
19935                has_ancestor_kind(declaration, "preproc_ifdef"),
19936                recovered,
19937                "{source}"
19938            );
19939            if !recovered {
19940                assert!(!tree.root_node().has_error());
19941                let open = tree.root_node().named_child(0).unwrap();
19942                assert_eq!(open.kind(), "preproc_linkage_open");
19943                assert!(open.child_by_field_name("value").is_some());
19944            }
19945            assert_eq!(
19946                callable_preprocessor_context_is_visible(declaration, source),
19947                visible,
19948                "{source}"
19949            );
19950        }
19951    }
19952
19953    #[test]
19954    #[hardwon("#6902: grammar 0.24.4 parsed an unnamed enum's base as its name")]
19955    fn resolver_anonymous_enum_base_grammar_boundary() {
19956        let source = "typedef unsigned Underlying; enum : Underlying { Value };";
19957        let tree = parse_cpp(source);
19958        assert!(
19959            !tree.root_node().has_error(),
19960            "{}",
19961            tree.root_node().to_sexp()
19962        );
19963        let start = source.find("Underlying {").unwrap();
19964        let base = tree
19965            .root_node()
19966            .named_descendant_for_byte_range(start, start + "Underlying".len())
19967            .unwrap();
19968        let enumeration = base.parent().unwrap();
19969        assert_eq!(enumeration.kind(), "enum_specifier");
19970        assert!(enumeration.child_by_field_name("name").is_none());
19971        assert_eq!(enumeration.child_by_field_name("base"), Some(base));
19972        assert!(!is_declaration_name(base));
19973    }
19974
19975    #[test]
19976    #[hardwon(
19977        "e3c20990a105: quicksilver round 4 on brokk-bifrost-cpp: spliced macro tokens need physical source coordinates"
19978    )]
19979    fn macro_splices_preserve_statement_origin_ranges() {
19980        for newline in ["\n", "\r\n"] {
19981            for (replacement, name, written) in [
19982                (
19983                    format!("jo\\{newline}ined\\{newline}();\\{newline}after();"),
19984                    "joined",
19985                    format!("jo\\{newline}ined"),
19986                ),
19987                (
19988                    format!("do {{ if (ready) bl\\{newline}ock }} while (0)"),
19989                    "block",
19990                    format!("bl\\{newline}ock"),
19991                ),
19992            ] {
19993                let parameters = vec!["block".to_owned()];
19994                let (source, tree, original_offsets) =
19995                    VisibilityIndex::parse_macro_replacement_body(&replacement, &parameters)
19996                        .expect("joined replacement parses");
19997                let body = ParsedReplacementBody {
19998                    source,
19999                    tree,
20000                    body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
20001                    parameters,
20002                    original_offsets,
20003                };
20004                let nodes = crate::grammar_test_support::nodes(body.tree.root_node());
20005                let matching: Vec<_> = nodes
20006                    .iter()
20007                    .filter(|node| {
20008                        node.kind() == "identifier" && node_text(**node, &body.source) == name
20009                    })
20010                    .collect();
20011                assert_eq!(matching.len(), 1, "{replacement}");
20012                let range = body.file_range(*matching[0], 0);
20013                assert_eq!(&replacement[range], written);
20014                for node in nodes.iter().filter(|node| {
20015                    node.kind() == "identifier" && node_text(**node, &body.source) == "after"
20016                }) {
20017                    assert_eq!(&replacement[body.file_range(*node, 0)], "after");
20018                }
20019            }
20020        }
20021    }
20022
20023    #[test]
20024    #[hardwon("05fbd05c6bd7: C++ declaration recovery regressed for this structured syntax edge")]
20025    fn issue_3089_statement_formal_at_end_of_replacement() {
20026        let parameters = vec!["handle".to_owned(), "block".to_owned()];
20027        for replacement in [
20028            "do { header_event_t* event; if ((handle)->active) block } while (0)",
20029            "do { header_event_t* event; block } while (0)",
20030        ] {
20031            assert!(
20032                super::VisibilityIndex::parse_macro_replacement_body(replacement, &parameters)
20033                    .is_some(),
20034                "{replacement}"
20035            );
20036        }
20037    }
20038    use super::*;
20039    use brokk_bifrost_test_macros::hardwon;
20040
20041    #[test]
20042    #[hardwon("2e5169325a14: C++ declaration recovery regressed for this structured syntax edge")]
20043    fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
20044        let source = "int size(void) { return sizeof(((Payload))); }\n";
20045        let mut parser = Parser::new();
20046        parser
20047            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20048            .expect("C++ grammar");
20049        let tree = parser.parse(source, None).expect("fixture tree");
20050        let start = source.find("Payload").expect("sizeof operand");
20051        let node = tree
20052            .root_node()
20053            .named_descendant_for_byte_range(start, start + "Payload".len())
20054            .expect("focused operand");
20055        let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
20056        let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
20057
20058        assert_eq!(node.kind(), "identifier");
20059        assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
20060        assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
20061    }
20062
20063    #[test]
20064    fn recovered_macro_actuals_keep_their_cst_boundaries() {
20065        for (source, recovered) in [
20066            ("int f() { return M(Type, value); }", false),
20067            ("int f() { return M(Type, ^(int n){ return n; }); }", true),
20068        ] {
20069            let tree = parse_cpp(source);
20070            let first = named_node_at(&tree, source, "Type");
20071            let (call, index, actual) = c_function_macro_argument(first)
20072                .unwrap_or_else(|| panic!("first actual {source}: {}", tree.root_node().to_sexp()));
20073            assert_eq!(index, 0);
20074            assert_eq!(node_text(actual, source), "Type");
20075            let arguments = call.child_by_field_name("arguments").unwrap();
20076            let raw = arguments.named_child(0).unwrap();
20077            if recovered {
20078                assert_eq!(raw.kind(), "binary_expression");
20079                assert_eq!(raw.child_by_field_name("left"), Some(actual));
20080                let inside = named_node_at(&tree, source, "return n;");
20081                let (_, index, block) = c_function_macro_argument(inside).expect("block actual");
20082                assert_eq!(index, 1);
20083                assert_eq!(block.kind(), "compound_literal_expression");
20084                assert_eq!(node_text(block, source), "(int n){ return n; }");
20085                assert_eq!(raw.child_by_field_name("right"), Some(block));
20086            } else {
20087                assert_eq!(raw, actual);
20088                let second = named_node_at(&tree, source, "value");
20089                let (_, index, actual) = c_function_macro_argument(second).expect("second actual");
20090                assert_eq!(index, 1);
20091                assert_eq!(node_text(actual, source), "value");
20092            }
20093        }
20094    }
20095
20096    fn parse_cpp(source: &str) -> Tree {
20097        crate::diagnostics::parse_cpp_tree(source)
20098    }
20099
20100    #[test]
20101    #[hardwon("#4271: source splitting included a new-array declarator in the allocation type")]
20102    fn cpp_new_expression_type_comes_from_the_structured_type_field() {
20103        let source = "auto value = new  ns::Box<int> [ 3 ];";
20104        let tree = parse_cpp(source);
20105        let mut pending = vec![tree.root_node()];
20106        let allocation = loop {
20107            let node = pending.pop().expect("new expression in fixture");
20108            if node.kind() == "new_expression" {
20109                break node;
20110            }
20111            let mut cursor = node.walk();
20112            pending.extend(
20113                node.named_children(&mut cursor)
20114                    .collect::<Vec<_>>()
20115                    .into_iter()
20116                    .rev(),
20117            );
20118        };
20119
20120        let type_node = cpp_new_expression_type_node(allocation).expect("structured type field");
20121        assert_eq!(allocation.child_by_field_name("type"), Some(type_node));
20122        assert_eq!(node_text(type_node, source), "ns::Box<int>");
20123        assert_eq!(
20124            allocation
20125                .child_by_field_name("declarator")
20126                .expect("array declarator")
20127                .kind(),
20128            "new_declarator"
20129        );
20130
20131        let old_split = normalize_cpp_whitespace(node_text(allocation, source));
20132        let old_split = old_split
20133            .strip_prefix("new ")
20134            .unwrap_or(old_split.as_str())
20135            .split(['(', '{'])
20136            .next()
20137            .expect("old split result");
20138        assert_ne!(old_split.trim(), node_text(type_node, source));
20139    }
20140
20141    fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
20142        let start = source.find(needle).expect("fixture needle");
20143        tree.root_node()
20144            .named_descendant_for_byte_range(start, start + needle.len())
20145            .expect("node at needle")
20146    }
20147
20148    fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
20149        let tree = crate::diagnostics::parse_cpp_tree(source);
20150        PreparedSyntaxTree::new(
20151            PreparedSyntaxSource::Exact(Arc::from(source)),
20152            tree,
20153            compute_line_starts(source),
20154            LanguageDialect::Standard(Language::Cpp),
20155            PreparedSourceOrigin::Disk,
20156            None,
20157        )
20158    }
20159
20160    fn unresolved_include_before(source: &str, reference: &str) -> bool {
20161        let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
20162        let prepared = prepared_cpp(source);
20163        let facts = collect_structured_include_facts(&prepared);
20164        let include_targets = IncludeTargetIndex::build([&file]);
20165        has_unresolved_include_visible_before_in_prepared(
20166            &file,
20167            &prepared,
20168            &include_targets,
20169            &facts,
20170            source.find(reference).expect("reference fixture"),
20171            &|guards| guards.cloned(),
20172        )
20173    }
20174
20175    #[test]
20176    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20177    fn unresolved_include_before_reference_is_visible() {
20178        let source = "#include \"missing.h\"\nint use = Missing;\n";
20179        assert!(unresolved_include_before(source, "Missing"));
20180    }
20181
20182    #[test]
20183    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20184    fn unresolved_include_after_reference_is_not_visible() {
20185        let source = "int use = Missing;\n#include \"missing.h\"\n";
20186        assert!(!unresolved_include_before(source, "Missing"));
20187    }
20188
20189    #[test]
20190    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20191    fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
20192        let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
20193        assert!(!unresolved_include_before(source, "Missing"));
20194    }
20195
20196    #[test]
20197    #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20198    fn unresolved_include_in_current_branch_is_visible() {
20199        let source =
20200            "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
20201        assert!(unresolved_include_before(source, "Missing"));
20202    }
20203
20204    /// Two macro-decorated class heads make tree-sitter close `detail` at the
20205    /// first class's `}`, `matchers` at the second's, and `app` at `detail`'s
20206    /// real `}`; the tail parses at translation-unit level and the two real
20207    /// closes for `matchers` and `app` land in a trailing ERROR (#1537).
20208    const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
20209namespace matchers {
20210    namespace detail {
20211        class API [[nodiscard]] First {
20212        public:
20213            int value() const { return count_ + 1; }
20214        private:
20215            int count_;
20216        };
20217        class API [[nodiscard]] Second {
20218        public:
20219            int value() const { return count_ + 2; }
20220        private:
20221            int count_;
20222        };
20223    } // namespace detail
20224
20225    template <typename T>
20226    void tail_function(MatcherBase<T> const& value);
20227
20228    class TailClass {};
20229} // namespace matchers
20230} // namespace app
20231
20232struct AfterAll {};
20233"#;
20234
20235    #[test]
20236    #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
20237    fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
20238        let source = STOLEN_BRACE_CASCADE;
20239        let tree = parse_cpp(source);
20240        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
20241
20242        let tail_class = named_node_at(&tree, source, "TailClass");
20243        assert!(
20244            !has_ancestor_kind(tail_class, "namespace_definition"),
20245            "the fixture must reproduce the recovery: the tail has no namespace ancestor"
20246        );
20247        let displaced = named_node_at(&tree, source, "Second");
20248        assert_eq!(
20249            enclosing_namespace_components(displaced, source),
20250            Some(vec!["app".to_string(), "matchers".to_string()]),
20251            "the fixture must displace the second class out of detail"
20252        );
20253
20254        let components = |needle: &str| {
20255            index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
20256        };
20257        assert_eq!(components("First"), ["app", "matchers", "detail"]);
20258        assert_eq!(components("Second"), ["app", "matchers", "detail"]);
20259        assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
20260        assert_eq!(components("tail_function"), ["app", "matchers"]);
20261        assert_eq!(components("TailClass"), ["app", "matchers"]);
20262        assert!(components("AfterAll").is_empty());
20263    }
20264
20265    #[test]
20266    #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
20267    fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
20268        let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
20269        let tree = parse_cpp(clean);
20270        assert!(!tree.root_node().has_error());
20271        assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
20272
20273        // A namespace that merely contains a parse error closes where its
20274        // brace says; the declarations after it keep their parsed scope.
20275        let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
20276        let tree = parse_cpp(damaged);
20277        assert!(tree.root_node().has_error());
20278        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
20279        assert_eq!(
20280            index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
20281            ["a"]
20282        );
20283    }
20284
20285    /// A function-like `#define` in a class body, whose replacement returns a
20286    /// SFINAE type, makes tree-sitter give up on the whole enclosing namespace:
20287    /// `namespace app {` becomes an `ERROR` whose own `namespace`, name and `{`
20288    /// tokens are the only trace of the head, and every declaration the
20289    /// namespace holds becomes their flat sibling. This is Catch2's
20290    /// `catch_decomposer.hpp`, reduced (issue #3084).
20291    const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
20292
20293    class Target {
20294        int value_;
20295    };
20296
20297    template <typename T>
20298    class Holder {
20299    public:
20300        explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
20301
20302#define HOLDER_DEFINE_OP( id, op )                                             \
20303    template <typename U>                                                      \
20304    constexpr friend auto operator op( Holder&& lhs, U&& rhs )                 \
20305        -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> {       \
20306        return Target{};                                                       \
20307    }
20308
20309        HOLDER_DEFINE_OP( equal, == )
20310#undef HOLDER_DEFINE_OP
20311        T m_lhs;
20312    };
20313
20314    class Tail {};
20315}
20316"#;
20317
20318    #[test]
20319    #[hardwon("a438f125e172: C++ declaration recovery regressed for this structured syntax edge")]
20320    fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
20321        let source = COLLAPSED_NAMESPACE_HEAD;
20322        let tree = parse_cpp(source);
20323        let target = named_node_at(&tree, source, "class Target");
20324
20325        // The macro-declaration grammar keeps the namespace head and its
20326        // members inside a surviving `namespace_definition` even with the
20327        // macro invocation shredding one declaration inside the body, so the
20328        // scope authority answers from parsed ancestry; the recovered-region
20329        // path this fixture exercised is no longer needed for it.
20330        assert!(
20331            tree.root_node().has_error(),
20332            "the fixture must retain the macro invocation's parse damage"
20333        );
20334        assert!(
20335            has_ancestor_kind(target, "namespace_definition"),
20336            "the namespace head survives: the class keeps its parsed namespace ancestor"
20337        );
20338
20339        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
20340        assert_eq!(
20341            index.enclosing_namespace_components(target, source),
20342            ["app"]
20343        );
20344        assert_eq!(
20345            index.enclosing_namespace_levels(target, source),
20346            vec![(
20347                "app".to_string(),
20348                Range {
20349                    start_byte: 0,
20350                    end_byte: source.len() - 1,
20351                    start_line: 1,
20352                    end_line: 25,
20353                },
20354                Range {
20355                    start_byte: 10,
20356                    end_byte: 13,
20357                    start_line: 1,
20358                    end_line: 1,
20359                },
20360            )],
20361            "the recovered namespace owns its full construct, not its first member or just its header"
20362        );
20363    }
20364
20365    #[test]
20366    #[hardwon("#5868: a literal namespace head inside a damaged function wrapper lost its owner")]
20367    fn namespace_body_name_components_recovers_literal_function_head() {
20368        // Independently authored direct-helper contract, not an index or
20369        // short-body reproduction. The index deliberately leaves clean
20370        // subtrees intact; head recognition alone does not make this simple
20371        // body an indexed recovery region. Corpus index/short-body/outside
20372        // ownership remains required by the private root golden prewriter.
20373        let source = "OPAQUE_PREFIX\nnamespace recovered\n{\nint member;\n}\nint member;\n";
20374        let tree = parse_cpp(source);
20375        let keyword_start = source.find("namespace recovered").unwrap();
20376        let keyword = tree
20377            .root_node()
20378            .named_descendant_for_byte_range(keyword_start, keyword_start + "namespace".len())
20379            .expect("literal keyword node");
20380        // The macro-declaration grammar reads the opaque prefix as a
20381        // `macro_modifier` and the literal `namespace` keyword as the bogus
20382        // function's type: a keyword cannot be a type, so the type field is
20383        // the same recovery evidence the older grammar's `ERROR` carried.
20384        assert_eq!(keyword.kind(), "type_identifier");
20385        assert_eq!(keyword.child_count(), 0);
20386        assert!(!keyword.is_missing() && !keyword.has_error());
20387        assert_eq!(node_text(keyword, source), "namespace");
20388        let wrapper = keyword.parent().expect("actual wrapper");
20389        assert_eq!(wrapper.kind(), "function_definition");
20390        let mut cursor = wrapper.walk();
20391        let children = wrapper
20392            .children(&mut cursor)
20393            .filter(|child| child.kind() != "comment")
20394            .collect::<Vec<_>>();
20395        assert_eq!(
20396            children.len(),
20397            4,
20398            "all named and unnamed children accounted: {children:?}"
20399        );
20400        let prefix = children[0];
20401        assert_eq!(prefix.kind(), "macro_modifier");
20402        assert!(!prefix.is_missing() && !prefix.has_error());
20403        assert_eq!(node_text(prefix, source), "OPAQUE_PREFIX");
20404        assert_eq!(children[1], keyword);
20405        assert_eq!(wrapper.child_by_field_name("type"), Some(keyword));
20406        let name = children[2];
20407        assert!(matches!(name.kind(), "identifier" | "namespace_identifier"));
20408        assert_eq!(name.child_count(), 0);
20409        assert!(!name.is_missing() && !name.has_error());
20410        assert_eq!(node_text(name, source), "recovered");
20411        assert_eq!(wrapper.child_by_field_name("declarator"), Some(name));
20412        let body = wrapper.child_by_field_name("body").expect("actual body");
20413        assert_eq!(children[3], body);
20414        assert_eq!(body.kind(), "compound_statement");
20415        assert!(!body.is_missing());
20416        let open = body.child(0).expect("actual first child");
20417        assert_eq!(open.kind(), "{");
20418        assert!(!open.is_missing() && !open.has_error());
20419        assert_eq!(open.start_byte(), body.start_byte());
20420        let (head, names) = namespace_body_name_components(wrapper, body, source)
20421            .expect("strict synthetic literal head recognized");
20422        assert_eq!(head, keyword);
20423        assert_eq!(
20424            names
20425                .iter()
20426                .map(|(name, _)| name.as_str())
20427                .collect::<Vec<_>>(),
20428            ["recovered"]
20429        );
20430        assert_eq!(
20431            (names[0].1.start_byte, names[0].1.end_byte),
20432            (name.start_byte(), name.end_byte())
20433        );
20434        let inside = named_node_at(&tree, source, "int member;");
20435        assert!(body.start_byte() < inside.start_byte() && inside.end_byte() < body.end_byte());
20436    }
20437
20438    #[test]
20439    #[hardwon("#5868: refusal controls must actually exercise the namespace body helper")]
20440    fn orphaned_namespace_scope_index_refuses_unproven_function_heads() {
20441        for refusal in [
20442            "int ordinary(int value) { return value; }",
20443            "OPAQUE_PREFIX namespace_ recovered { int member; }",
20444            "OPAQUE_PREFIX namespace recovered other { int member; }",
20445            "OPAQUE_PREFIX namespace recovered(int value) { return value; }",
20446        ] {
20447            let tree = parse_cpp(refusal);
20448            let mut stack = vec![tree.root_node()];
20449            let mut helper_calls = 0;
20450            while let Some(node) = stack.pop() {
20451                if node.kind() == "function_definition"
20452                    && let Some(body) = node.child_by_field_name("body")
20453                {
20454                    helper_calls += 1;
20455                    assert_eq!(
20456                        (node.start_byte(), node.end_byte()),
20457                        (0, refusal.len()),
20458                        "guard control must exercise its complete envelope: source={refusal}, node={node:?}"
20459                    );
20460                    assert!(
20461                        namespace_body_name_components(node, body, refusal).is_none(),
20462                        "refused shape acquired namespace: source={refusal}, node={node:?}, body={body:?}"
20463                    );
20464                }
20465                let mut cursor = node.walk();
20466                stack.extend(node.named_children(&mut cursor));
20467            }
20468            assert_eq!(
20469                helper_calls,
20470                1,
20471                "guard control must reach exactly its actual function/body helper call: source={refusal}, tree={:?}",
20472                tree.root_node()
20473            );
20474        }
20475
20476        // The measured '=' case has no function/body envelope. A missing
20477        // body is likewise parser-absence coverage, not a passed helper guard.
20478        // The macro-declaration grammar reads both as clean declarations whose
20479        // specifier words are `macro_modifier`s, so the parser-absence premise
20480        // is now their lack of a function/body envelope rather than an ERROR.
20481        for absent in [
20482            "OPAQUE_PREFIX namespace recovered = other { int member; }",
20483            "OPAQUE_PREFIX namespace recovered int member;",
20484        ] {
20485            let tree = parse_cpp(absent);
20486            let mut stack = vec![tree.root_node()];
20487            let mut helper_candidates = 0;
20488            while let Some(node) = stack.pop() {
20489                if node.kind() == "function_definition"
20490                    && node.child_by_field_name("body").is_some()
20491                {
20492                    helper_candidates += 1;
20493                }
20494                let mut cursor = node.walk();
20495                stack.extend(node.named_children(&mut cursor));
20496            }
20497            assert_eq!(
20498                helper_candidates,
20499                0,
20500                "parser-absence control unexpectedly acquired a helper envelope: source={absent}, tree={:?}",
20501                tree.root_node()
20502            );
20503        }
20504    }
20505
20506    /// A macro that opens a block: `APP_IF_ACTIVE` expands to `if (active) {`,
20507    /// so the `{` of a scope the source really opens is part of the macro's
20508    /// `preproc_arg` and never becomes a `{` token. The tree therefore holds
20509    /// one more real `}` than real `{`, and a file-global stack has to assign
20510    /// those closes to the wrong opens: the `}` on line 9 takes `check`'s body,
20511    /// `check`'s own `}` on line 11 takes `detail`, line 15 takes `app`, and
20512    /// line 16 arrives with nothing open. This is the state simdjson's
20513    /// `compile_time_json-inl.h` reaches eight times over (#3328).
20514    const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
20515
20516namespace app {
20517namespace detail {
20518
20519bool check(bool active) {
20520  APP_IF_ACTIVE
20521    return true;
20522  }
20523  return false;
20524}
20525
20526class Tail {};
20527
20528} // namespace detail
20529} // namespace app
20530"#;
20531
20532    /// simdjson's `include/simdjson/compile_time_json-inl.h` at `9b33047a`,
20533    /// reduced. ABI 15 with runtime 0.25.10 now keeps each case-label character
20534    /// literal and the nested namespace definitions. The malformed function
20535    /// body still causes a missing outer namespace close. Keep the original
20536    /// source as a positive for real inner closes, without inventing the outer
20537    /// close that the parser could not read (#3328).
20538    const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
20539namespace compile_time {
20540namespace number_parsing {
20541consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
20542    auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
20543      auto digit = [](uint8_t c) -> uint32_t {
20544      }
20545    }
20546    if (a) {
20547      while (a) {
20548      }
20549    }
20550    switch (c) {
20551    case '{': {
20552    }
20553    }
20554  }
20555  while (a) {
20556    cursor += field.second;
20557    if (a) {
20558    case '{': {
20559      if (a) {
20560      }
20561    }
20562    case 'n': {
20563      if (a) {
20564      }
20565    }
20566    }
20567  }
20568  if (a) {
20569    simdjson_consteval_error("Expected '}'");
20570  }
20571}
20572}
20573}
20574"#;
20575
20576    /// Real `{` and `}` tokens of a tree, the stream the index pairs.
20577    fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
20578        let mut stack = vec![root];
20579        let mut counts = (0, 0);
20580        while let Some(node) = stack.pop() {
20581            match (node.kind(), node.is_missing()) {
20582                ("{", false) => counts.0 += 1,
20583                ("}", false) => counts.1 += 1,
20584                _ => {}
20585            }
20586            let mut cursor = node.walk();
20587            stack.extend(node.children(&mut cursor));
20588        }
20589        counts
20590    }
20591
20592    /// The open brace of `head`, which is the key its close lands under.
20593    fn namespace_open_byte(source: &str, head: &str) -> usize {
20594        let at = source.find(head).expect("fixture namespace head");
20595        at + head.len() - 1
20596    }
20597
20598    fn assert_namespace_heads_have_no_matching_close(root: Node<'_>, source: &str, heads: &[&str]) {
20599        let index = OrphanedNamespaceScopeIndex::build(root, source);
20600        for head in heads {
20601            assert_eq!(
20602                index.matching_close_brace(namespace_open_byte(source, head)),
20603                None,
20604                "{head} must not claim a close belonging to an inner scope"
20605            );
20606        }
20607    }
20608
20609    #[test]
20610    #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
20611    fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
20612        let source = MACRO_OPENED_SCOPE;
20613        let tree = parse_cpp(source);
20614        let root = tree.root_node();
20615        assert_eq!(
20616            real_brace_token_counts(root),
20617            (4, 5),
20618            "the fixture must reproduce the loss: one scope the source opens \
20619             has no `{{` token, so the stream holds an extra real close"
20620        );
20621
20622        assert_namespace_heads_have_no_matching_close(
20623            root,
20624            source,
20625            &["namespace app {", "namespace detail {"],
20626        );
20627    }
20628
20629    #[test]
20630    #[hardwon("5312adbcf9bf: C++ declaration recovery regressed for this structured syntax edge")]
20631    fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
20632        // The stack proves no pairing in this file, so every restored level
20633        // keeps its best-effort scope. None of it may reach a declaration the
20634        // file writes after its last close.
20635        let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
20636        let tree = parse_cpp(&source);
20637        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
20638        let after_all = named_node_at(&tree, &source, "struct AfterAll");
20639        assert!(
20640            index
20641                .enclosing_namespace_components(after_all, &source)
20642                .is_empty()
20643        );
20644    }
20645
20646    #[test]
20647    #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
20648    fn orphaned_namespace_scope_index_preserves_structured_reflection_closes() {
20649        let source = REFLECTION_CASE_LABEL_RECOVERY;
20650        let tree = parse_cpp(source);
20651        let root = tree.root_node();
20652        let mut stack = vec![root];
20653        let mut mis_lexed_opens = Vec::new();
20654        while let Some(node) = stack.pop() {
20655            if node.kind() == "character"
20656                && &source[node.start_byte()..node.end_byte()] == "{"
20657                && node
20658                    .parent()
20659                    .is_some_and(|parent| parent.kind() != "char_literal")
20660            {
20661                mis_lexed_opens.push(node.start_position().row + 1);
20662            }
20663            let mut cursor = node.walk();
20664            stack.extend(node.children(&mut cursor));
20665        }
20666        assert!(
20667            mis_lexed_opens.is_empty(),
20668            "ABI15 should keep each case-label `{{` inside its `char_literal`; \
20669             the old ABI14 mis-lex is no longer a valid recovery witness"
20670        );
20671        assert_eq!(
20672            real_brace_token_counts(root),
20673            (17, 17),
20674            "the improved parse retains balanced real brace tokens"
20675        );
20676        assert_eq!(root.kind(), "translation_unit");
20677        assert!(root.has_error(), "the malformed body remains incomplete");
20678        let index = OrphanedNamespaceScopeIndex::build(root, source);
20679        assert_eq!(
20680            index.matching_close_brace(namespace_open_byte(source, "namespace simdjson {")),
20681            None,
20682            "a missing outer close must not borrow an inner namespace's real close"
20683        );
20684        for (head, close) in [
20685            ("namespace compile_time {", source.len() - 2),
20686            ("namespace number_parsing {", source.len() - 4),
20687        ] {
20688            assert_eq!(
20689                index
20690                    .matching_close_brace(namespace_open_byte(source, head))
20691                    .map(|range| range.start_byte),
20692                Some(close),
20693                "{head} retains its own real close"
20694            );
20695        }
20696    }
20697
20698    #[test]
20699    #[hardwon("#2243: an empty namespace could hide a nested indexed owner suffix")]
20700    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
20701        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
20702        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
20703        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
20704        assert!(indexed_namespace_path_is_recoverable(
20705            &["cache".to_string()],
20706            &indexed,
20707            1,
20708        ));
20709    }
20710
20711    #[test]
20712    #[hardwon("#1876: lookup ordering was partial and changed with unrelated workspace content")]
20713    fn sort_lookup_units_totally_orders_every_identity_field() {
20714        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
20715        let base = CodeUnit::with_signature(
20716            file.clone(),
20717            CodeUnitType::Function,
20718            "scope",
20719            "value",
20720            Some("()".to_string()),
20721            false,
20722        );
20723        let different_kind = CodeUnit::with_signature(
20724            file.clone(),
20725            CodeUnitType::Field,
20726            "scope",
20727            "value",
20728            Some("()".to_string()),
20729            false,
20730        );
20731        let synthetic = base.with_synthetic(true);
20732
20733        let interner = segment_interner();
20734        let mut member_fq = FqName::new();
20735        member_fq.push(interner.intern("scope", SegmentKind::Package));
20736        member_fq.push(interner.intern("value", SegmentKind::Member));
20737        let different_package_boundary = CodeUnit::from_fq(
20738            file.clone(),
20739            CodeUnitType::Function,
20740            member_fq,
20741            0,
20742            Some("()".to_string()),
20743            false,
20744        );
20745
20746        let mut unknown_fq = FqName::new();
20747        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
20748        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
20749        let different_segment_kind = CodeUnit::from_fq(
20750            file,
20751            CodeUnitType::Function,
20752            unknown_fq,
20753            1,
20754            Some("()".to_string()),
20755            false,
20756        );
20757
20758        let input = vec![
20759            base,
20760            different_kind,
20761            synthetic,
20762            different_package_boundary,
20763            different_segment_kind,
20764        ];
20765        let mut expected = input.clone();
20766        sort_lookup_units(&mut expected);
20767        assert!(expected.windows(2).all(|pair| {
20768            let mut ordered = pair.to_vec();
20769            sort_lookup_units(&mut ordered);
20770            ordered == pair && pair[0] != pair[1]
20771        }));
20772
20773        let mut reversed = input.clone();
20774        reversed.reverse();
20775        sort_lookup_units(&mut reversed);
20776        assert_eq!(reversed, expected);
20777
20778        let mut rotated = input;
20779        rotated.rotate_left(2);
20780        sort_lookup_units(&mut rotated);
20781        assert_eq!(rotated, expected);
20782    }
20783
20784    #[test]
20785    #[hardwon("6ffe85773652: C++ declaration recovery regressed for this structured syntax edge")]
20786    fn structured_conditional_array_bound_preserves_the_real_guard() {
20787        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";
20788        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
20789        let parse = |source: &str| {
20790            let mut parser = Parser::new();
20791            parser
20792                .set_language(&tree_sitter_cpp::LANGUAGE.into())
20793                .expect("C++ grammar");
20794            parser.parse(source, None).expect("fixture tree")
20795        };
20796
20797        let tree = parse(damaged);
20798        let root = tree.root_node();
20799        assert!(
20800            !root.has_error(),
20801            "the native conditional-array grammar now reads the original recovery witness cleanly: {}",
20802            root.to_sexp()
20803        );
20804        let target = damaged.find("target").expect("target byte");
20805        let declaration = root
20806            .descendant_for_byte_range(target, target + "target".len())
20807            .and_then(|mut node| {
20808                loop {
20809                    if node.kind() == "declaration" {
20810                        break Some(node);
20811                    }
20812                    node = node.parent()?;
20813                }
20814            })
20815            .expect("declaration after the displaced terminator");
20816        let outer = declaration
20817            .parent()
20818            .filter(|node| node.kind() == "preproc_ifdef")
20819            .expect("ordinary outer include guard");
20820        let bound = outer
20821            .named_children(&mut outer.walk())
20822            .filter(|node| node.kind() == "declaration")
20823            .filter_map(|node| node.child_by_field_name("declarator"))
20824            .find(|node| node.kind() == "array_declarator")
20825            .and_then(|node| node.child_by_field_name("size"))
20826            .expect("structured conditional array bound");
20827        assert_eq!(bound.kind(), "preproc_array_size");
20828        let conditional = bound
20829            .named_children(&mut bound.walk())
20830            .find(|node| node.kind() == "preproc_ifdef")
20831            .expect("array-size conditional");
20832        assert!(conditional.end_byte() <= declaration.start_byte());
20833        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20834        assert!(
20835            !std::iter::successors(Some(declaration), |node| node.parent())
20836                .any(|ancestor| same_node(ancestor, conditional)),
20837            "the declaration must not inherit the array-size feature guard"
20838        );
20839        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
20840        assert!(preprocessor_conditional_contains_descendant(
20841            outer,
20842            declaration
20843        ));
20844
20845        let tree = parse(guarded);
20846        let conditional = tree
20847            .root_node()
20848            .named_child(0)
20849            .filter(|node| node.kind() == "preproc_ifdef")
20850            .expect("ordinary conditional");
20851        let declaration = conditional
20852            .named_children(&mut conditional.walk())
20853            .find(|node| node.kind() == "declaration")
20854            .expect("guarded declaration");
20855        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20856        assert!(preprocessor_conditional_contains_descendant(
20857            conditional,
20858            declaration
20859        ));
20860
20861        let damaged_alternative = format!(
20862            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
20863            "UNUSED(value)\n".repeat(64)
20864        );
20865        let tree = parse(&damaged_alternative);
20866        let conditional = tree
20867            .root_node()
20868            .named_child(0)
20869            .filter(|node| node.kind() == "preproc_ifdef")
20870            .expect("outer conditional with an alternative");
20871        assert!(conditional.has_error());
20872        assert!(conditional.child_by_field_name("alternative").is_some());
20873        assert!(
20874            conditional
20875                .child(conditional.child_count() - 1)
20876                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
20877        );
20878        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20879
20880        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";
20881        let tree = parse(split_declaration);
20882        let root = tree.root_node();
20883        // The macro-declaration grammar folds the whole split declaration
20884        // into one damaged `type_definition` instead of keeping a
20885        // `preproc_ifdef` with a displaced terminator: the boundary the
20886        // recovery supplied is now the declaration node itself. What must not
20887        // change is the outcome -- the function written after the split
20888        // declaration is outside the damaged span and inherits no guard.
20889        let split = root
20890            .named_children(&mut root.walk())
20891            .find(|node| node.kind() == "type_definition")
20892            .expect("the split declaration folds into one type definition");
20893        assert_eq!(
20894            split.end_position().row + 1,
20895            9,
20896            "the split declaration ends at its own declarator line"
20897        );
20898        let target = split_declaration
20899            .find("static int target")
20900            .expect("target byte");
20901        assert!(split.end_byte() <= target);
20902        let target_node = root
20903            .descendant_for_byte_range(target, target + "static".len())
20904            .expect("target node");
20905        assert!(
20906            !std::iter::successors(Some(target_node), |node| node.parent())
20907                .any(|ancestor| ancestor.kind() == "preproc_ifdef" && ancestor.has_error()),
20908            "the declaration after the shredded split declaration must not \
20909             inherit a damaged conditional ancestor"
20910        );
20911    }
20912
20913    #[test]
20914    #[hardwon("6ba7704de3ed: C++ declaration recovery regressed for this structured syntax edge")]
20915    fn fragmented_reference_guard_is_recovered() {
20916        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";
20917        let mut parser = Parser::new();
20918        parser
20919            .set_language(&tree_sitter_cpp::LANGUAGE.into())
20920            .expect("C++ grammar");
20921        let tree = parser.parse(source, None).expect("fixture tree");
20922        let start = source.rfind("helper").expect("reference byte");
20923        let node = tree
20924            .root_node()
20925            .descendant_for_byte_range(start, start + "helper".len())
20926            .expect("reference node");
20927        let mut expected = HashSet::default();
20928        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
20929            vec![
20930                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
20931                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
20932            ],
20933        )));
20934        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
20935    }
20936
20937    #[test]
20938    #[hardwon(
20939        "#6325: grammar 0.24.4 displaced #else/#elif into a declaration ERROR and the branch boundary vanished"
20940    )]
20941    fn displaced_branch_directives_decide_guards_without_proving_unreadable_branches() {
20942        let parse = |source: &str| {
20943            let mut parser = Parser::new();
20944            parser
20945                .set_language(&tree_sitter_cpp::LANGUAGE.into())
20946                .expect("C++ grammar");
20947            parser.parse(source, None).expect("fixture tree")
20948        };
20949        fn at<'tree>(root: Node<'tree>, source: &str, needle: &str, token: &str) -> Node<'tree> {
20950            let start = source.find(needle).expect("needle") + needle.find(token).expect("token");
20951            root.descendant_for_byte_range(start, start + token.len())
20952                .expect("token node")
20953        }
20954        let guards = |node: Node<'_>, source: &str| preprocessor_guard_environment(node, source);
20955        let set = |items: &[PreprocessorGuard]| items.iter().cloned().collect::<HashSet<_>>();
20956
20957        // `#ifdef`: the `#else` is an ERROR child of the declaration that spans
20958        // both sentinels, and a `#define` after it stays a structured child of
20959        // the same conditional.
20960        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";
20961        let tree = parse(source);
20962        let root = tree.root_node();
20963        let x = at(root, source, "int x", "x");
20964        let conditional = std::iter::successors(Some(x), |node| node.parent())
20965            .find(|node| node.kind() == "preproc_ifdef")
20966            .expect("ifdef");
20967        assert!(conditional.child_by_field_name("alternative").is_none());
20968        let directive = cpp_displaced_preprocessor_branch(conditional).expect("displaced #else");
20969        assert_eq!(directive.kind(), "#else");
20970        assert_eq!(directive.parent().map(|node| node.kind()), Some("ERROR"));
20971        let defined = PreprocessorGuard::Defined("A".to_string());
20972        assert_eq!(guards(x, source), Some(set(std::slice::from_ref(&defined))));
20973        let class_name = at(root, source, "class T", "T");
20974        assert_eq!(guards(class_name, source), Some(set(&[defined.negated()])));
20975        let define = at(root, source, "#define MODE", "MODE")
20976            .parent()
20977            .filter(|node| node.kind() == "preproc_def")
20978            .expect("macro event");
20979        let mut cache = PreprocessorContextCache::default();
20980        assert!(matches!(
20981            preprocessor_branch_of_descendant(conditional, define, &mut cache),
20982            PreprocessorBranch::DisplacedElse
20983        ));
20984        assert_eq!(
20985            preprocessor_conditional_family_for_declaration_with_cache(class_name, &mut cache),
20986            Some(conditional),
20987            "a displaced #else completes the family"
20988        );
20989        assert_eq!(
20990            callable_guard_completion_byte(class_name, source),
20991            Some(conditional.end_byte())
20992        );
20993
20994        // A displaced `#elif B` reads `B` as the declaration's type: its
20995        // condition has no structured node, so nothing after it, including the
20996        // later displaced `#else`, may be read as `!A`.
20997        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";
20998        let tree = parse(source);
20999        let root = tree.root_node();
21000        let x = at(root, source, "int x", "x");
21001        let conditional = std::iter::successors(Some(x), |node| node.parent())
21002            .find(|node| node.kind() == "preproc_if")
21003            .expect("if");
21004        assert!(conditional.child_by_field_name("alternative").is_none());
21005        assert_eq!(
21006            cpp_displaced_preprocessor_branch(conditional).map(|node| node.kind()),
21007            Some("#elif")
21008        );
21009        let truthy = PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy("A".to_string()));
21010        assert_eq!(guards(x, source), Some(set(&[truthy])));
21011        let mut cache = PreprocessorContextCache::default();
21012        for needle in ["class T", "struct T"] {
21013            let name = at(root, source, needle, "T");
21014            assert_eq!(
21015                guards(name, source),
21016                None,
21017                "{needle} after a displaced #elif"
21018            );
21019            assert!(matches!(
21020                preprocessor_branch_of_descendant(conditional, name, &mut cache),
21021                PreprocessorBranch::Unreadable
21022            ));
21023            assert_eq!(
21024                preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache),
21025                None,
21026                "{needle}: an unreadable #elif branch does not complete the family"
21027            );
21028            assert_eq!(callable_guard_completion_byte(name, source), None);
21029        }
21030
21031        // A structured `#elif` whose own `#else` was displaced: the content
21032        // after it excludes both readable conditions.
21033        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";
21034        let tree = parse(source);
21035        let root = tree.root_node();
21036        let start = source.rfind("class T").expect("else class") + "class ".len();
21037        let name = root
21038            .descendant_for_byte_range(start, start + 1)
21039            .expect("else class name");
21040        let elif = std::iter::successors(Some(name), |node| node.parent())
21041            .find(|node| node.kind() == "preproc_elif")
21042            .expect("structured elif");
21043        assert_eq!(
21044            cpp_displaced_preprocessor_branch(elif).map(|node| node.kind()),
21045            Some("#else")
21046        );
21047        assert_eq!(
21048            guards(name, source),
21049            Some(set(&[
21050                PreprocessorGuard::Defined("A".to_string()).negated(),
21051                PreprocessorGuard::Defined("B".to_string()).negated(),
21052            ]))
21053        );
21054        let mut cache = PreprocessorContextCache::default();
21055        assert!(
21056            preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache).is_some()
21057        );
21058    }
21059
21060    #[test]
21061    #[hardwon(
21062        "#2184: grammar 0.24.4 reads a macro-decorated return type cleanly, so the recovered-only same-file alias gate stopped matching"
21063    )]
21064    fn macro_modifier_decorated_type_matches_the_recovered_gate_scope() {
21065        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";
21066        let mut parser = Parser::new();
21067        parser
21068            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21069            .expect("C++ grammar");
21070        let tree = parser.parse(source, None).expect("fixture tree");
21071        let root = tree.root_node();
21072        let type_before = |declared: &str| {
21073            let end = source.find(declared).expect("declared name");
21074            let start = source[..end].rfind('T').expect("type token");
21075            root.descendant_for_byte_range(start, start + 1)
21076                .expect("type node")
21077        };
21078        for declared in ["make(", "value;", "p)", "body("] {
21079            let node = type_before(declared);
21080            assert_eq!(node.kind(), "type_identifier", "{declared}");
21081            assert!(
21082                macro_modifier_decorated_type_node(node),
21083                "{declared}: {}",
21084                node.parent().expect("declaration").to_sexp()
21085            );
21086        }
21087        for declared in ["plain(", "qualified("] {
21088            let node = type_before(declared);
21089            assert!(
21090                !macro_modifier_decorated_type_node(node),
21091                "{declared} is outside the macro-decorated head scope: {}",
21092                node.parent().expect("parent").to_sexp()
21093            );
21094        }
21095    }
21096
21097    #[test]
21098    fn expression_defined_and_ifndef_guards_are_incompatible() {
21099        let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
21100        let mut parser = Parser::new();
21101        parser
21102            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21103            .expect("C++ grammar");
21104        let tree = parser.parse(source, None).expect("fixture tree");
21105        let root = tree.root_node();
21106        let selected_start = source.find("selected").expect("selected declaration");
21107        let rejected_start = source.find("rejected").expect("rejected declaration");
21108        let selected = root
21109            .descendant_for_byte_range(selected_start, selected_start + "selected".len())
21110            .expect("selected node");
21111        let rejected = root
21112            .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
21113            .expect("rejected node");
21114        let selected_guards =
21115            preprocessor_guard_environment(selected, source).expect("selected guards");
21116        let rejected_guards =
21117            preprocessor_guard_environment(rejected, source).expect("rejected guards");
21118
21119        assert!(
21120            merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
21121            "opposite spellings of one macro guard must contradict"
21122        );
21123    }
21124
21125    #[test]
21126    #[hardwon("02b3765c2a06: C++ declaration recovery regressed for this structured syntax edge")]
21127    fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
21128        let source = r#"#ifdef _WIN32
21129#if defined(__cplusplus)
21130extern "C"
21131#endif
21132int platform_api(void);
21133#endif
21134
21135#ifdef _WIN32
21136static int entropy_target(void) { return 0; }
21137#else
21138#ifdef HAVE_COMMON_RANDOM
21139static int other_target(void) { return 0; }
21140#elif defined(HAVE_GETENTROPY)
21141static int entropy_target(void) { return 1; }
21142static int use_entropy(void) { return entropy_target(); }
21143#endif
21144#endif
21145"#;
21146        let mut parser = Parser::new();
21147        parser
21148            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21149            .expect("C++ grammar");
21150        let tree = parser.parse(source, None).expect("fixture tree");
21151        let start = source.rfind("entropy_target()").expect("reference");
21152        let node = tree
21153            .root_node()
21154            .descendant_for_byte_range(start, start + "entropy_target".len())
21155            .expect("reference node");
21156        let guards = preprocessor_guard_environment(node, source).expect("active C branch");
21157        assert!(
21158            guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
21159            "{guards:#?}"
21160        );
21161        assert!(
21162            guards.contains(&PreprocessorGuard::Undefined(
21163                "HAVE_COMMON_RANDOM".to_string()
21164            )),
21165            "{guards:#?}"
21166        );
21167        assert!(
21168            guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
21169            "{guards:#?}"
21170        );
21171        assert!(
21172            !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
21173            "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
21174        );
21175    }
21176
21177    #[test]
21178    #[hardwon("1e74d25492c1: C++ declaration recovery regressed for this structured syntax edge")]
21179    fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
21180        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";
21181        let mut parser = Parser::new();
21182        parser
21183            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21184            .expect("C++ grammar");
21185        let tree = parser.parse(source, None).expect("fixture tree");
21186        let root = tree.root_node();
21187        let node_at = |text: &str, start: usize| {
21188            root.descendant_for_byte_range(start, start + text.len())
21189                .expect("token node")
21190        };
21191
21192        let key_start = source.find("case KEY").expect("case label") + "case ".len();
21193        let guard_start = source.find("ENABLE_KEYS").expect("guard name");
21194        assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
21195        assert!(!is_ordinary_macro_reference_node(node_at(
21196            "ENABLE_KEYS",
21197            guard_start,
21198        )));
21199    }
21200
21201    #[test]
21202    #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
21203    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
21204        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";
21205        let mut parser = Parser::new();
21206        parser
21207            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21208            .expect("C++ grammar");
21209        let tree = parser.parse(source, None).expect("fixture tree");
21210        let root = tree.root_node();
21211        let definition_start = source.find("target(void)").expect("definition");
21212        let reference_start = source.rfind("target()").expect("reference");
21213        let definition = root
21214            .descendant_for_byte_range(definition_start, definition_start + "target".len())
21215            .expect("definition node");
21216        let reference = root
21217            .descendant_for_byte_range(reference_start, reference_start + "target".len())
21218            .expect("reference node");
21219        let required =
21220            preprocessor_guard_environment(definition, source).expect("definition guard");
21221        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
21222        assert!(guard_requirements_hold_at_reference(
21223            &required,
21224            Some(&active)
21225        ));
21226    }
21227
21228    #[test]
21229    #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
21230    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
21231        let source = "g_autoptr(FuChunkArray) self = make_array();";
21232        let mut parser = Parser::new();
21233        parser
21234            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21235            .expect("C++ grammar");
21236        let tree = parser.parse(source, None).expect("fixture tree");
21237        let statement = tree.root_node().named_child(0).expect("statement");
21238        let binding =
21239            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
21240        assert_eq!(binding.name, "self");
21241        assert_eq!(binding.type_name, "FuChunkArray");
21242        assert_eq!(binding.pointer_depth, 1);
21243
21244        let near_miss = "holder(FuChunkArray) self = make_array();";
21245        let tree = parser.parse(near_miss, None).expect("near-miss tree");
21246        let statement = tree.root_node().named_child(0).expect("statement");
21247        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
21248    }
21249
21250    #[test]
21251    #[hardwon("dc1b8c5a6ee8: C++ declaration recovery regressed for this structured syntax edge")]
21252    fn boolean_guard_normalization_proves_equivalence_and_implication() {
21253        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
21254        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
21255        let negated_windows_branch =
21256            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
21257        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
21258        assert_eq!(negated_windows_branch, portable);
21259
21260        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
21261        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
21262        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
21263        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
21264        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
21265        assert!(fallback_branch.implies(&fallback_declaration));
21266        assert!(
21267            BooleanGuardExpression::Truthy("FEATURE".to_string())
21268                .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
21269        );
21270        assert!(
21271            BooleanGuardExpression::Undefined("FEATURE".to_string())
21272                .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
21273        );
21274        assert!(
21275            !BooleanGuardExpression::Defined("FEATURE".to_string())
21276                .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
21277        );
21278        assert!(!fallback_declaration.implies(&fallback_branch));
21279    }
21280
21281    #[test]
21282    #[hardwon("15e5ef579cdd: C++ declaration recovery regressed for this structured syntax edge")]
21283    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
21284        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";
21285        let mut parser = Parser::new();
21286        parser
21287            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21288            .expect("C++ grammar");
21289        let tree = parser.parse(source, None).expect("fixture tree");
21290        let root = tree.root_node();
21291        let call = |marker: &str| {
21292            let start = source.find(marker).expect("call marker");
21293            let mut node = root
21294                .descendant_for_byte_range(start, start + "helper".len())
21295                .expect("call name node");
21296            loop {
21297                if node.kind() == "call_expression" {
21298                    break node;
21299                }
21300                node = node.parent().expect("call expression ancestor");
21301            }
21302        };
21303        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
21304        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
21305        let keyword_call = call("helper(NULL, template); /* bound */");
21306        let keyword_arguments = keyword_call
21307            .child_by_field_name("arguments")
21308            .expect("keyword argument list");
21309        assert_eq!(
21310            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
21311            1
21312        );
21313
21314        let nested_call = call("helper(NULL, template); /* nested bound */");
21315        let nested_arguments = nested_call
21316            .child_by_field_name("arguments")
21317            .expect("nested keyword argument list");
21318        assert_eq!(
21319            recovered_c_keyword_argument_count(&c_file, nested_call, nested_arguments, source),
21320            1
21321        );
21322
21323        let lambda_call = call("helper(NULL, template); }(); /* lambda scope */");
21324        let lambda_arguments = lambda_call
21325            .child_by_field_name("arguments")
21326            .expect("lambda keyword argument list");
21327        assert_eq!(
21328            recovered_c_keyword_argument_count(&c_file, lambda_call, lambda_arguments, source),
21329            0,
21330            "a lambda body must not borrow the enclosing collapsed C parameter"
21331        );
21332
21333        assert_eq!(
21334            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
21335            0
21336        );
21337
21338        let unbound_call = call("helper(NULL, template); /* unbound */");
21339        let unbound_arguments = unbound_call
21340            .child_by_field_name("arguments")
21341            .expect("unbound argument list");
21342        assert_eq!(
21343            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
21344            0
21345        );
21346    }
21347
21348    #[test]
21349    #[brokk_bifrost_test_macros::hardwon(
21350        "#4407: collapsed C keyword parameters need structural position proof"
21351    )]
21352    fn c_keyword_argument_recovery_rejects_non_parameter_keyword_positions() {
21353        let source = r#"static int helper(const char *left, wchar_t *right) { return 0; }
21354int type_keyword(template *value) {
21355    return helper(NULL, template); /* type */
21356}
21357int qualifier_keyword(const *template) {
21358    return helper(NULL, template); /* qualifier */
21359}
21360int annotation_keyword(wchar_t *value __attribute__((template))) {
21361    return helper(NULL, template); /* annotation */
21362}
21363int default_keyword(wchar_t *value = template) {
21364    return helper(NULL, template); /* default */
21365}
21366int nested_expression(wchar_t *value, (template)) {
21367    return helper(NULL, template); /* nested */
21368}
21369int malformed_head = (wchar_t *template) {
21370    return helper(NULL, template); /* malformed */
21371};
21372"#;
21373        let tree = parse_cpp(source);
21374        let root = tree.root_node();
21375        let call = |marker: &str| {
21376            let start = source.find(marker).expect("call marker");
21377            let mut node = root
21378                .descendant_for_byte_range(start, start + "helper".len())
21379                .expect("call name node");
21380            loop {
21381                if node.kind() == "call_expression" {
21382                    break Some(node);
21383                }
21384                let Some(parent) = node.parent() else {
21385                    break None;
21386                };
21387                node = parent;
21388            }
21389        };
21390        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-near-miss.c");
21391        let markers = [
21392            ("helper(NULL, template); /* type */", true),
21393            ("helper(NULL, template); /* qualifier */", false),
21394            ("helper(NULL, template); /* annotation */", false),
21395            ("helper(NULL, template); /* default */", false),
21396            ("helper(NULL, template); /* nested */", false),
21397            ("helper(NULL, template); /* malformed */", false),
21398        ];
21399        let mut exercised = 0;
21400        for (marker, expect_call) in markers {
21401            // The macro-declaration grammar admits the misplaced keyword's
21402            // neighbors as `macro_modifier`s, which flattens these heads and
21403            // their bodies: where no `call_expression` survives, the recovery
21404            // has no invocation to fire on, which is itself the non-admission
21405            // proof. Pin the split so the near-miss matrix cannot silently
21406            // decay: only the `type` marker retains an invocation.
21407            let Some(keyword_call) = call(marker) else {
21408                assert!(
21409                    !expect_call,
21410                    "{marker} lost its call_expression; the near-miss matrix changed"
21411                );
21412                continue;
21413            };
21414            assert!(
21415                expect_call,
21416                "{marker} unexpectedly retains a call_expression; the near-miss matrix changed"
21417            );
21418            exercised += 1;
21419            let keyword_arguments = keyword_call
21420                .child_by_field_name("arguments")
21421                .expect("keyword argument list");
21422            assert_eq!(
21423                recovered_c_keyword_argument_count(
21424                    &c_file,
21425                    keyword_call,
21426                    keyword_arguments,
21427                    source,
21428                ),
21429                0,
21430                "keyword in {marker} must not prove a parameter binding"
21431            );
21432        }
21433        assert!(
21434            exercised > 0,
21435            "at least one near-miss marker must exercise the count itself"
21436        );
21437    }
21438
21439    #[test]
21440    #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
21441    fn c_function_declarator_recovery_accepts_invocations_not_binders() {
21442        let source = r#"#define MAKE(type) type *value
21443MAKE(int *);
21444typedef struct Item Item;
21445struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
21446void gen_op_movl(void *s, int first, int second) { }
21447const char *strZ(const char *value) { return value; }
21448int body(void *s) {
21449    MAKE(int *);
21450    gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
21451                offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
21452    execvp(strZ(value), UNCONSTIFY(char **, args));
21453}
21454 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
21455    if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
21456int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
21457    if (d->units) {
21458        switch (target->nodetype) {
21459        case 1:
21460        case 2:
21461            break;
21462        default:
21463            AMEND_WRONG_NODETYPE("deviation", "replace", "units");
21464        }
21465        DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
21466        lysdict_remove(ctx, ((struct Item *)target)->units);
21467        DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
21468    }
21469     return 0;
21470 }
21471STATIC EFI_STATUS Encode () { return 0; }
21472"#;
21473        let tree = parse_cpp(source);
21474        let top_macro_start = source.find("MAKE(int *);").expect("top macro");
21475        let top_macro = tree
21476            .root_node()
21477            .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
21478            .expect("top macro node");
21479        let body_macro_start = source
21480            .match_indices("MAKE(int *);")
21481            .nth(1)
21482            .expect("body macro")
21483            .0;
21484        let body_macro = tree
21485            .root_node()
21486            .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
21487            .expect("body macro node");
21488        let function_call_start = source
21489            .find("gen_op_movl(s, offsetof(CPUX86State")
21490            .expect("function call");
21491        let function_call = tree
21492            .root_node()
21493            .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
21494            .expect("function call node");
21495        let strz_start = source.find("strZ(value)").expect("nested function call");
21496        let strz = tree
21497            .root_node()
21498            .named_descendant_for_byte_range(strz_start, strz_start + 4)
21499            .expect("nested function call node");
21500        let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
21501        let recovered_call = tree
21502            .root_node()
21503            .named_descendant_for_byte_range(
21504                recovered_call_start,
21505                recovered_call_start + "lysdict_remove".len(),
21506            )
21507            .expect("recovered call node");
21508        let binder_start = source.find("Encode").expect("binder");
21509        let binder = tree
21510            .root_node()
21511            .named_descendant_for_byte_range(binder_start, binder_start + 6)
21512            .expect("binder node");
21513
21514        assert!(recovered_c_function_declarator_invocation(top_macro));
21515        assert!(recovered_c_function_declarator_invocation(body_macro));
21516        assert!(recovered_c_function_declarator_invocation(function_call));
21517        assert!(recovered_c_function_declarator_invocation(strz));
21518        assert!(recovered_c_function_declarator_invocation(recovered_call));
21519        assert!(!recovered_c_function_declarator_invocation(binder));
21520    }
21521
21522    #[test]
21523    #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
21524    fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
21525        let source = r#"typedef int krb5_context;
21526int helper(int first, int second) { return first + second; }
21527static krb5_context ctx;
21528int main(int argc, char **argv) {
21529    int ccinitial;
21530    const char *collection_name, *typename;
21531    typename = helper(ctx, ccinitial);
21532    return 0;
21533}
21534"#;
21535        let tree = parse_cpp(source);
21536        let ctx = tree
21537            .root_node()
21538            .descendant_for_byte_range(
21539                source.find("ctx, ccinitial").expect("ctx argument"),
21540                source.find("ctx, ccinitial").expect("ctx argument") + 3,
21541            )
21542            .expect("ctx node");
21543        let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
21544        let ccinitial = tree
21545            .root_node()
21546            .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
21547            .expect("sibling node");
21548        let typename = named_node_at(&tree, source, "typename = helper");
21549        let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
21550
21551        // The macro-declaration grammar retains the argument beneath the
21552        // nested `ERROR` as a `type_descriptor`, so its type position is a
21553        // `type_identifier`; the recovery contract below is unchanged.
21554        assert_eq!(ctx.kind(), "type_identifier");
21555        assert!(recovered_c_parenthesized_declarator_reference(ctx));
21556        assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
21557        assert!(!recovered_c_parenthesized_declarator_reference(typename));
21558        assert!(!recovered_c_parenthesized_declarator_reference(helper));
21559    }
21560
21561    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
21562        let mut parser = Parser::new();
21563        parser
21564            .set_language(&tree_sitter_cpp::LANGUAGE.into())
21565            .expect("C++ grammar");
21566        let tree = parser.parse(source, None).expect("C++ fixture tree");
21567        let mut stack = vec![tree.root_node()];
21568        while let Some(node) = stack.pop() {
21569            if node.kind() == "enum_specifier" {
21570                return flattened_macro_namespace_components(node, source);
21571            }
21572            let mut cursor = node.walk();
21573            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
21574            stack.extend(children.into_iter().rev());
21575        }
21576        None
21577    }
21578
21579    #[test]
21580    #[hardwon("0e6df36dfa5d: C++ declaration recovery regressed for this structured syntax edge")]
21581    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
21582        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
21583namespace detail
21584{
21585enum class value_t { null };
21586}
21587NLOHMANN_JSON_NAMESPACE_END
21588NLOHMANN_JSON_NAMESPACE_BEGIN
21589namespace next
21590{
21591struct next_type {};
21592}
21593NLOHMANN_JSON_NAMESPACE_END
21594"#;
21595        assert_eq!(
21596            first_enum_flattened_namespace(complete),
21597            Some(vec!["detail".to_string()])
21598        );
21599
21600        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
21601        assert_eq!(
21602            first_enum_flattened_namespace(&stale_end),
21603            Some(vec!["detail".to_string()]),
21604            "a stale end marker before the begin marker must not replace the intended namespace"
21605        );
21606
21607        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
21608namespace detail
21609{
21610enum class value_t { null };
21611}
21612struct next_type {};
21613"#;
21614        assert_eq!(first_enum_flattened_namespace(incomplete), None);
21615    }
21616}
21617
21618/// Comparator laws for the total C++ lookup order introduced by #1876.
21619///
21620/// `sort_lookup_units` is the single tie-break the C++ resolver applies before
21621/// any "first wins" selection (template families in #1836, the visible
21622/// identifier index, the type-candidate lists). If its comparator is not a
21623/// total order over CodeUnit identity, some pair stays tied and the survivor
21624/// falls back to the order the units arrived in -- which is FxHash iteration
21625/// order over keys whose hash covers the absolute workspace root. That is the
21626/// exact mechanism behind #1836 and the #414 / #432 heisenbug, so the laws are
21627/// checked generatively rather than on one hand-picked list.
21628///
21629/// CodeUnit identity is `source`, `kind`, `fq`, `package_segment_count`,
21630/// `signature` and `synthetic` (see `impl PartialEq for CodeUnit`); a CodeUnit
21631/// carries no range, so declaration ranges are covered by the workspace-level
21632/// property in `tests/suite_analyzers/determinism_properties.rs` instead.
21633#[cfg(test)]
21634mod lookup_order_properties {
21635    use super::*;
21636    use proptest::prelude::*;
21637
21638    /// Segment spellings the C++ extractor and the shared renderer actually
21639    /// produce, including the `$`-joined nested spellings and non-ASCII
21640    /// identifiers that a byte-wise comparison has to keep apart.
21641    const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
21642    const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
21643    /// Two roots so the order is pinned across workspaces as well as inside
21644    /// one: the root path is precisely the byte string that used to leak into
21645    /// iteration order.
21646    const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
21647    const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
21648    const KINDS: [CodeUnitType; 6] = [
21649        CodeUnitType::Class,
21650        CodeUnitType::Function,
21651        CodeUnitType::Field,
21652        CodeUnitType::Module,
21653        CodeUnitType::Macro,
21654        CodeUnitType::FileScope,
21655    ];
21656
21657    /// Where one unit sits relative to another under the comparator that
21658    /// `sort_lookup_units` owns.
21659    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
21660    enum ProbedOrder {
21661        Before,
21662        Tied,
21663        After,
21664        /// Both directions reported "strictly first": the comparator is not
21665        /// dual, and no sort over it can be order-independent.
21666        Contradictory,
21667    }
21668
21669    impl ProbedOrder {
21670        fn mirror(self) -> Self {
21671            match self {
21672                ProbedOrder::Before => ProbedOrder::After,
21673                ProbedOrder::After => ProbedOrder::Before,
21674                other => other,
21675            }
21676        }
21677
21678        /// -1 / 0 / +1, so transitivity reads as the `<= 0` law.
21679        fn signum(self) -> i8 {
21680            match self {
21681                ProbedOrder::Before => -1,
21682                ProbedOrder::Tied => 0,
21683                ProbedOrder::After => 1,
21684                ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
21685            }
21686        }
21687    }
21688
21689    /// Read the comparator through its only caller.
21690    ///
21691    /// `sort_lookup_units` is a stable sort, so for a two-element slice the
21692    /// output says exactly whether the comparator put the second element
21693    /// strictly first. Sorting both arrangements of one pair therefore reports
21694    /// the comparator's verdict in both directions, including the contradictory
21695    /// case a single sort would hide.
21696    fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
21697        if left == right {
21698            // A stable sort cannot distinguish two equal values, and `Equal` is
21699            // the only verdict a total order can give them.
21700            return ProbedOrder::Tied;
21701        }
21702        let mut forward = vec![left.clone(), right.clone()];
21703        sort_lookup_units(&mut forward);
21704        let mut backward = vec![right.clone(), left.clone()];
21705        sort_lookup_units(&mut backward);
21706        let left_first = backward[0] == *left;
21707        let right_first = forward[0] == *right;
21708        match (left_first, right_first) {
21709            (true, true) => ProbedOrder::Contradictory,
21710            (true, false) => ProbedOrder::Before,
21711            (false, true) => ProbedOrder::After,
21712            (false, false) => ProbedOrder::Tied,
21713        }
21714    }
21715
21716    fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
21717        (
21718            0..ROOT_NAMES.len(),
21719            0..REL_PATHS.len(),
21720            0..KINDS.len(),
21721            prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
21722            0..3usize,
21723            0..SIGNATURES.len(),
21724            any::<bool>(),
21725        )
21726            .prop_map(
21727                |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
21728                    let source = ProjectFile::new(
21729                        std::env::temp_dir().join(ROOT_NAMES[root]),
21730                        REL_PATHS[rel_path],
21731                    );
21732                    let interner = segment_interner();
21733                    let mut fq = FqName::new();
21734                    for (atom, segment_kind) in &segments {
21735                        fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
21736                    }
21737                    // `from_fq` requires a non-empty declaration tail.
21738                    let package_segment_count = package_prefix % fq.len();
21739                    CodeUnit::from_fq(
21740                        source,
21741                        KINDS[kind],
21742                        fq,
21743                        package_segment_count,
21744                        SIGNATURES[signature].map(str::to_string),
21745                        synthetic,
21746                    )
21747                },
21748            )
21749    }
21750
21751    proptest! {
21752        #![proptest_config(ProptestConfig::with_cases(256))]
21753
21754        /// Reflexivity and duality: a unit ties with itself, and no pair is
21755        /// strictly first in both directions.
21756        #[test]
21757        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21758        fn lookup_order_is_reflexive_and_dual(
21759            left in code_unit_strategy(),
21760            right in code_unit_strategy(),
21761        ) {
21762            prop_assert_eq!(
21763                probe_order(&left, &left),
21764                ProbedOrder::Tied,
21765                "a unit must tie with itself: {:?}",
21766                left
21767            );
21768            let forward = probe_order(&left, &right);
21769            prop_assert_ne!(
21770                forward,
21771                ProbedOrder::Contradictory,
21772                "comparator put each of these strictly first: left={:?} right={:?}",
21773                left,
21774                right
21775            );
21776            prop_assert_eq!(
21777                probe_order(&right, &left),
21778                forward.mirror(),
21779                "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
21780                left,
21781                right
21782            );
21783        }
21784
21785        /// Transitivity: `a <= b` and `b <= c` imply `a <= c`.
21786        #[test]
21787        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21788        fn lookup_order_is_transitive(
21789            a in code_unit_strategy(),
21790            b in code_unit_strategy(),
21791            c in code_unit_strategy(),
21792        ) {
21793            let ab = probe_order(&a, &b);
21794            let bc = probe_order(&b, &c);
21795            let ac = probe_order(&a, &c);
21796            for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
21797                prop_assert_ne!(
21798                    probed,
21799                    ProbedOrder::Contradictory,
21800                    "comparator is not dual over {}: a={:?} b={:?} c={:?}",
21801                    pair,
21802                    a,
21803                    b,
21804                    c
21805                );
21806            }
21807            if ab.signum() <= 0 && bc.signum() <= 0 {
21808                prop_assert!(
21809                    ac.signum() <= 0,
21810                    "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
21811                     a={:?} b={:?} c={:?}",
21812                    ab,
21813                    bc,
21814                    ac,
21815                    a,
21816                    b,
21817                    c
21818                );
21819            }
21820        }
21821
21822        /// The property #1876 exists for: only identical identities may tie.
21823        /// A tie between distinct units is the residual hash-order dependence.
21824        #[test]
21825        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21826        fn lookup_order_separates_distinct_identities(
21827            left in code_unit_strategy(),
21828            right in code_unit_strategy(),
21829        ) {
21830            if probe_order(&left, &right) == ProbedOrder::Tied {
21831                prop_assert_eq!(
21832                    &left,
21833                    &right,
21834                    "distinct identities tied, so their order is whatever order they \
21835                     arrived in: left_segments={:?} right_segments={:?}",
21836                    left.fq_segments_debug(),
21837                    right.fq_segments_debug()
21838                );
21839            }
21840        }
21841
21842        /// The consequence the resolver relies on: the sorted list is a
21843        /// function of the SET of units, not of the order they were pushed in.
21844        #[test]
21845        #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21846        fn lookup_sort_is_permutation_invariant(
21847            units in prop::collection::vec(code_unit_strategy(), 1..=8),
21848        ) {
21849            let mut sorted = units.clone();
21850            sort_lookup_units(&mut sorted);
21851            for rotation in 0..units.len() {
21852                for reversed in [false, true] {
21853                    let mut permuted = units.clone();
21854                    permuted.rotate_left(rotation);
21855                    if reversed {
21856                        permuted.reverse();
21857                    }
21858                    sort_lookup_units(&mut permuted);
21859                    prop_assert_eq!(
21860                        &permuted,
21861                        &sorted,
21862                        "sorting a permutation gave a different list \
21863                         (rotation={}, reversed={}): input={:?}",
21864                        rotation,
21865                        reversed,
21866                        units
21867                    );
21868                }
21869            }
21870        }
21871    }
21872}
21873
21874#[cfg(test)]
21875mod resolver_domain_properties {
21876    use super::*;
21877    use proptest::prelude::*;
21878
21879    proptest! {
21880        // A missing free variable cannot be supplied by irrelevant names.
21881        // The expected fully bound tree is built directly from the inputs.
21882        #[test]
21883        fn template_substitution_respects_free_variables(
21884            first in 0u16..1000,
21885            second in 1000u16..2000,
21886            unrelated in prop::collection::vec(0u16..1000, 1..8),
21887            rename in "[A-Z][a-z]{0,6}",
21888        ) {
21889            let atom = |value: u16| CppTemplateTerm::Atom {
21890                kind: "number_literal".to_owned(), text: value.to_string(),
21891            };
21892            let term = CppTemplateTerm::Node {
21893                kind: "template_argument_list".to_owned(),
21894                children: vec![CppTemplateTerm::Parameter("T".to_owned()), CppTemplateTerm::Parameter(format!("U{rename}"))],
21895            };
21896            let mut bindings = HashMap::default();
21897            bindings.insert("T".to_owned(), atom(first));
21898            prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), None);
21899            for (index, value) in unrelated.into_iter().enumerate() {
21900                bindings.insert(format!("unrelated{index}"), atom(value));
21901                prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), None);
21902            }
21903            bindings.insert(format!("U{rename}"), atom(second));
21904            let expected = CppTemplateTerm::Node {
21905                kind: "template_argument_list".to_owned(), children: vec![atom(first), atom(second)],
21906            };
21907            prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), Some(expected));
21908        }
21909
21910        // Combining redeclarations is symmetric. When their parameter lists
21911        // agree, the accepted argument counts are the union of both domains.
21912        #[test]
21913        fn callable_arity_merge_is_a_symmetric_domain_union(
21914            total in 0usize..20,
21915            left_required in 0usize..20,
21916            right_required in 0usize..20,
21917            left_variadic in any::<bool>(),
21918            right_variadic in any::<bool>(),
21919        ) {
21920            let left = CallableArity::new(left_required.min(total), total, left_variadic);
21921            let right = CallableArity::new(right_required.min(total), total, right_variadic);
21922            let merged = merge_compatible_callable_arities(left, right);
21923            prop_assert_eq!(merged, merge_compatible_callable_arities(right, left));
21924            if let Some(merged) = merged {
21925                for count in 0..=total + 2 {
21926                    prop_assert_eq!(merged.accepts(count), left.accepts(count) || right.accepts(count));
21927                }
21928            } else {
21929                prop_assert_ne!(left_variadic, right_variadic);
21930            }
21931        }
21932
21933        // Repeating the same include-path assumptions cannot discharge an
21934        // undecidable guard. This is the idempotence law of conjunction.
21935        #[test]
21936        fn guard_environment_conjunction_is_idempotent(names in prop::collection::vec("[A-Z]{1,5}", 1..8)) {
21937            let guards: HashSet<_> = names.into_iter().map(PreprocessorGuard::Defined).collect();
21938            let environment = PreprocessorGuardEnvironment { all: guards.clone(), partial: guards.clone() };
21939            let twice = environment.merged(&environment).unwrap();
21940            prop_assert_eq!(&twice.all, &guards);
21941            prop_assert_eq!(&twice.partial, &guards);
21942        }
21943    }
21944}
21945
21946#[cfg(test)]
21947mod resolver_lookup_key_properties {
21948    use super::*;
21949    use proptest::prelude::*;
21950
21951    proptest! {
21952        #![proptest_config(ProptestConfig::with_cases(32))]
21953
21954        // The declaration producer and the bounded FQN consumer must agree
21955        // on at least one exact index key. Canonical-name fallback would mask
21956        // this boundary mismatch in ordinary navigation.
21957        #[test]
21958        fn parsed_nested_types_round_trip_through_bounded_lookup_keys(
21959            namespaces in 0usize..3,
21960            owners in 1usize..5,
21961            suffix in "[a-z]{1,6}",
21962        ) {
21963            let mut source = String::new();
21964            let mut components = Vec::new();
21965            for index in 0..namespaces {
21966                let name = format!("ns{index}_{suffix}");
21967                source.push_str(&format!("namespace {name} {{ "));
21968                components.push(name);
21969            }
21970            for index in 0..owners {
21971                let name = format!("Owner{index}");
21972                source.push_str(&format!("struct {name} {{ "));
21973                components.push(name);
21974            }
21975            source.push_str("struct Leaf {}; ");
21976            for _ in 0..owners { source.push_str("}; "); }
21977            for _ in 0..namespaces { source.push_str("} "); }
21978            components.push("Leaf".to_owned());
21979            source.push_str(&format!("{}* value;", components.join("::")));
21980
21981            let tree = crate::diagnostics::parse_cpp_tree(&source);
21982            prop_assert!(!tree.root_node().has_error());
21983            let file = ProjectFile::new(std::env::temp_dir(), "lookup-keys.cpp");
21984            let parsed = crate::adapter::parse_cpp_file(&file, &source, &tree);
21985            let leaf = parsed.declarations().iter()
21986                .find(|unit| unit.is_class() && unit.identifier() == "Leaf")
21987                .expect("declaration producer indexed the nested type");
21988            let declaration = tree.root_node().named_child(tree.root_node().named_child_count() - 1).unwrap();
21989            prop_assert_eq!(declaration.kind(), "declaration");
21990            let type_node = declaration.child_by_field_name("type").unwrap();
21991            let parts = cpp_type_name_components(type_node, &source).unwrap();
21992            prop_assert_eq!(&parts, &components);
21993            let candidates = cpp_reference_fqn_candidates_from_parts(&parts, TargetKind::Type);
21994            let declaration_key = leaf.fq_name();
21995            prop_assert!(candidates.contains(&declaration_key), "declaration key {:?} absent from bounded query keys {:?}", declaration_key, candidates);
21996        }
21997    }
21998}