Skip to main content

brokk_bifrost_cpp/graph/
resolver.rs

1use crate::call_match::{
2    CppArgType, cpp_signature_param_types, cpp_split_top_level_commas, normalize_cpp_type_name,
3};
4use crate::compile_context::CppCompileContext;
5#[cfg(test)]
6use crate::declarations::cpp_displaced_preprocessor_terminator;
7use crate::declarations::{
8    CppComparableNode, CppComparableParameter, CppComparableSlot, CppRecoveredExportClassIndex,
9    cpp_callable_identity_suffix, cpp_comparable_parameter_shapes, cpp_declarator_adds_indirection,
10    cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_field_declaration_linkage,
11    cpp_function_declarator_at, cpp_template_term, node_text, normalize_cpp_whitespace,
12    recovered_class_body_at, recovered_function_like_field_declarator,
13    recovered_pyobject_head_field,
14};
15use crate::graph::CppGraphSource;
16use crate::graph::extractor::ScanCtx;
17use crate::graph::syntax::{
18    function_macro_replacement_span, normalize_macro_continuations,
19    object_macro_replacement_type_references,
20};
21use crate::graph_support::CppSource;
22use crate::imports::{
23    IncludeTargetIndex, include_paths as cpp_include_paths, resolve_include_targets_with_index,
24};
25use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
26use brokk_bifrost_core::analyzer::model::{
27    CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
28    CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect, StructuredTypeName,
29};
30use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
31use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
32#[cfg(test)]
33use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
34use brokk_bifrost_core::analyzer::query_token::QueryToken;
35use brokk_bifrost_core::analyzer::structural::adapter_helpers::{field_name_in_parent, node_range};
36use brokk_bifrost_core::analyzer::tree_walk::{
37    ParentIndex, WalkControl, children_iter, named_children_iter, node_for_exact_range,
38    push_named_children_reversed, walk_named_tree_preorder,
39};
40use brokk_bifrost_core::analyzer::usages::common::same_node;
41use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
42use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
43use brokk_bifrost_core::cancellation::CancellationToken;
44use brokk_bifrost_core::hash::{HashMap, HashSet};
45#[cfg(test)]
46use brokk_bifrost_core::text_utils::compute_line_starts;
47use std::borrow::Cow;
48#[cfg(any(test, feature = "test-support"))]
49use std::cell::Cell;
50use std::cell::OnceCell;
51use std::cmp::Ordering as CmpOrdering;
52use std::collections::BTreeSet;
53use std::hash::Hash;
54use std::sync::atomic::{AtomicUsize, Ordering};
55use std::sync::{Arc, Mutex, OnceLock, RwLock};
56use std::time::{Duration, Instant};
57use tree_sitter::{Node, Parser, Tree};
58
59#[cfg(any(test, feature = "test-support"))]
60thread_local! {
61    static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
62    static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
63}
64
65#[derive(Clone, Copy, PartialEq, Eq)]
66pub enum TargetKind {
67    Type,
68    Constructor,
69    FreeFunction,
70    Method,
71    GlobalField,
72    MemberField,
73    Macro,
74}
75
76pub enum LexicalTypeResolution {
77    Resolved {
78        unit: CodeUnit,
79        components: Vec<String>,
80        candidates: Vec<CodeUnit>,
81    },
82    Ambiguous,
83    Missing,
84}
85
86#[derive(Clone, Copy)]
87enum TypeCandidateResolution<'a> {
88    Canonical,
89    PreserveAlias,
90    PreserveTarget(&'a CodeUnit),
91}
92
93/// Why a name did not reduce to one indexed type declaration.
94///
95/// The two answers are not interchangeable. `Ambiguous` means the index holds
96/// several declarations and the caller must choose; `Unresolvable` means the
97/// index holds none, which is a boundary the workspace cannot see past. A
98/// `using`/`typedef` alias to a template parameter or to a standard-library
99/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
100/// answer with an empty candidate list (#1828).
101#[derive(Clone, Copy, Debug, PartialEq, Eq)]
102enum TypeCandidateFailure {
103    Ambiguous,
104    Unresolvable,
105}
106
107impl TypeCandidateFailure {
108    fn lexical_resolution(self) -> LexicalTypeResolution {
109        match self {
110            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
111            Self::Unresolvable => LexicalTypeResolution::Missing,
112        }
113    }
114}
115
116pub enum LexicalCallableValueResolution {
117    Type(CodeUnit),
118    FreeFunction(CodeUnit),
119    Ambiguous,
120    Missing,
121}
122
123pub enum UsingEnumMemberResolution {
124    Resolved { owner: CodeUnit, member: CodeUnit },
125    Ambiguous,
126    Missing,
127}
128
129pub enum NamespaceValueResolution {
130    Resolved,
131    Ambiguous,
132    Missing,
133}
134
135#[derive(Clone, Debug, PartialEq, Eq)]
136pub enum OrdinaryMacroReferenceResolution {
137    Resolved(CodeUnit),
138    Ambiguous,
139    Missing,
140}
141
142#[derive(Clone, Debug, PartialEq, Eq)]
143pub enum RecoveredCReferenceRanges {
144    Complete(Vec<Range>),
145    LimitExceeded,
146}
147
148pub fn resolve_namespace_value(
149    analyzer: &CppGraphSource<'_>,
150    visibility: &VisibilityIndex<'_>,
151    file: &ProjectFile,
152    namespace: &str,
153    name: &str,
154    before_byte: usize,
155) -> NamespaceValueResolution {
156    let mut matches = Vec::new();
157    for candidate in visibility.visible_identifier_candidates(file, name) {
158        if type_owner_of(analyzer, candidate).is_some()
159            || candidate.package_name() != namespace
160            || (candidate.source() == file
161                && !analyzer
162                    .ranges(candidate)
163                    .iter()
164                    .any(|range| range.start_byte < before_byte))
165            || matches
166                .iter()
167                .any(|existing| same_visible_symbol(existing, candidate))
168        {
169            continue;
170        }
171        matches.push(candidate.clone());
172        if matches.len() > 1 {
173            return NamespaceValueResolution::Ambiguous;
174        }
175    }
176    matches
177        .pop()
178        .map(|_| NamespaceValueResolution::Resolved)
179        .unwrap_or(NamespaceValueResolution::Missing)
180}
181
182pub(crate) struct ScopedUsingEnumOwners {
183    scopes: Vec<Vec<CodeUnit>>,
184}
185
186/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
187/// Cross-file and inherited class imports are deliberately not inferred without persisted
188/// evidence; a missing imported enumerator therefore remains unproven rather than being
189/// misresolved.
190pub(crate) struct SemanticUsingEnumOwners {
191    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
192    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
193}
194
195pub(crate) enum SemanticUsingEnumMemberResolution {
196    Class(UsingEnumMemberResolution),
197    Namespace(UsingEnumMemberResolution),
198    Missing,
199}
200
201impl SemanticUsingEnumOwners {
202    pub(crate) fn new() -> Self {
203        Self {
204            class_imports: HashMap::default(),
205            namespace_imports: HashMap::default(),
206        }
207    }
208
209    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
210        let imports = self.class_imports.entry(class).or_default();
211        if !imports
212            .iter()
213            .any(|existing| same_visible_symbol(existing, &enum_owner))
214        {
215            imports.push(enum_owner);
216        }
217    }
218
219    pub fn import_namespace(
220        &mut self,
221        namespace: Vec<String>,
222        declaration_byte: usize,
223        enum_owner: CodeUnit,
224    ) {
225        let imports = self.namespace_imports.entry(namespace).or_default();
226        if !imports
227            .iter()
228            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
229        {
230            imports.push((declaration_byte, enum_owner));
231        }
232    }
233
234    pub fn resolve_member(
235        &self,
236        visibility: &VisibilityIndex<'_>,
237        file: &ProjectFile,
238        class: Option<&CodeUnit>,
239        namespace: &[String],
240        before_byte: usize,
241        name: &str,
242    ) -> SemanticUsingEnumMemberResolution {
243        if let Some(class) = class
244            && let Some((_, imports)) = self
245                .class_imports
246                .iter()
247                .find(|(owner, _)| same_visible_symbol(owner, class))
248        {
249            let resolution =
250                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
251            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
252                return SemanticUsingEnumMemberResolution::Class(resolution);
253            }
254        }
255        for prefix_len in (0..=namespace.len()).rev() {
256            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
257                continue;
258            };
259            let owners = imports
260                .iter()
261                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
262                .map(|(_, owner)| owner);
263            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
264            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
265                return SemanticUsingEnumMemberResolution::Namespace(resolution);
266            }
267        }
268        SemanticUsingEnumMemberResolution::Missing
269    }
270}
271
272fn resolve_using_enum_member_for_owners<'a>(
273    visibility: &VisibilityIndex<'_>,
274    file: &ProjectFile,
275    owners: impl IntoIterator<Item = &'a CodeUnit>,
276    name: &str,
277) -> UsingEnumMemberResolution {
278    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
279    for owner in owners {
280        for member in visibility.visible_members_for_owner_name(file, owner, name) {
281            if !member.is_field()
282                || matches.iter().any(|(existing_owner, existing_member)| {
283                    same_visible_symbol(existing_owner, owner)
284                        && same_visible_symbol(existing_member, member)
285                })
286            {
287                continue;
288            }
289            matches.push((owner.clone(), member.clone()));
290        }
291    }
292    match matches.len() {
293        0 => UsingEnumMemberResolution::Missing,
294        1 => {
295            let (owner, member) = matches.pop().expect("one using-enum match");
296            UsingEnumMemberResolution::Resolved { owner, member }
297        }
298        _ => UsingEnumMemberResolution::Ambiguous,
299    }
300}
301
302impl ScopedUsingEnumOwners {
303    pub(crate) fn new() -> Self {
304        Self {
305            scopes: vec![Vec::new()],
306        }
307    }
308
309    pub fn enter_scope(&mut self) {
310        self.scopes.push(Vec::new());
311    }
312
313    pub fn exit_scope(&mut self) {
314        if self.scopes.len() > 1 {
315            self.scopes.pop();
316        }
317    }
318
319    pub fn import(&mut self, owner: CodeUnit) {
320        let scope = self
321            .scopes
322            .last_mut()
323            .expect("using-enum scope stack is never empty");
324        if !scope
325            .iter()
326            .any(|existing| same_visible_symbol(existing, &owner))
327        {
328            scope.push(owner);
329        }
330    }
331
332    pub fn resolve_member(
333        &self,
334        visibility: &VisibilityIndex<'_>,
335        file: &ProjectFile,
336        name: &str,
337    ) -> UsingEnumMemberResolution {
338        for scope in self.scopes.iter().rev() {
339            let resolution =
340                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
341            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
342                return resolution;
343            }
344        }
345        UsingEnumMemberResolution::Missing
346    }
347}
348
349#[derive(Clone)]
350pub struct TargetSpec {
351    pub target: CodeUnit,
352    pub kind: TargetKind,
353    pub owner: Option<CodeUnit>,
354    pub member_name: String,
355    pub callable_arity: Option<CallableArity>,
356    pub activated_callable_arities: Vec<ActivatedCallableArity>,
357    pub param_types: Option<Vec<String>>,
358    pub enum_owner_kind: EnumOwnerKind,
359    pub owner_is_forward_declaration: bool,
360    pub callable_has_definition_body: bool,
361}
362
363#[derive(Clone, Copy)]
364pub struct ActivatedCallableArity {
365    pub activation_byte: usize,
366    pub arity: CallableArity,
367}
368
369#[derive(Debug, PartialEq, Eq, Hash)]
370pub struct TypeScanKey {
371    target: LogicalSymbolKey,
372    member_name: String,
373}
374
375#[derive(Clone, Debug, PartialEq, Eq, Hash)]
376struct LogicalSymbolKey {
377    kind: CodeUnitType,
378    fq_name: String,
379    signature: Option<String>,
380}
381
382struct ResolvedTypeOwner {
383    unit: CodeUnit,
384    is_forward_declaration: bool,
385}
386
387#[derive(Clone, Copy, PartialEq, Eq)]
388pub enum EnumOwnerKind {
389    Scoped,
390    Unscoped,
391    NonEnum,
392}
393
394impl TargetSpec {
395    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
396        (self.kind == TargetKind::Type).then(|| TypeScanKey {
397            target: logical_symbol_key(&self.target),
398            member_name: self.member_name.clone(),
399        })
400    }
401
402    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
403        if target.is_class() {
404            return Some(Self::new(
405                target.clone(),
406                TargetKind::Type,
407                Some(target.clone()),
408                target.identifier().to_string(),
409                None,
410                None,
411            ));
412        }
413
414        if target.is_field() {
415            // A namespace (module) is not a receiver: a namespace-scoped constant such as
416            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
417            // qualified from outside, exactly like a global. Treating a module owner as a
418            // member-field owner makes the receiver/owner-context match reject every valid
419            // reference, so resolve it as a global field instead.
420            let owner = type_owner_of(analyzer, target);
421            let kind = if owner.is_some() {
422                TargetKind::MemberField
423            } else {
424                TargetKind::GlobalField
425            };
426            let enum_owner_kind = owner
427                .as_ref()
428                .map(|owner| classify_enum_owner(analyzer, owner))
429                .unwrap_or(EnumOwnerKind::NonEnum);
430            let mut spec = Self::new(
431                target.clone(),
432                kind,
433                owner,
434                target.identifier().to_string(),
435                None,
436                None,
437            );
438            spec.enum_owner_kind = enum_owner_kind;
439            return Some(spec);
440        }
441
442        if target.is_function() {
443            // Free functions declared inside a namespace have a module owner; that namespace is
444            // not a call receiver, so resolve them as free functions rather than methods.
445            let owner_resolution = target_type_owner_resolution(analyzer, target);
446            let owner_is_forward_declaration = owner_resolution
447                .as_ref()
448                .is_some_and(|owner| owner.is_forward_declaration);
449            let owner = owner_resolution.map(|owner| owner.unit);
450            let kind = if owner.as_ref().is_some_and(|owner| {
451                target.identifier() == owner.identifier()
452                    || analyzer
453                        .cpp
454                        .and_then(|cpp| cpp.template_metadata(owner))
455                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
456            }) {
457                TargetKind::Constructor
458            } else if owner.is_some() {
459                TargetKind::Method
460            } else {
461                TargetKind::FreeFunction
462            };
463            let mut spec = Self::new(
464                target.clone(),
465                kind,
466                owner,
467                target.identifier().to_string(),
468                Some(cpp_callable_arity(analyzer, target)),
469                cpp_callable_parameter_types(analyzer, target),
470            );
471            spec.owner_is_forward_declaration = owner_is_forward_declaration;
472            spec.callable_has_definition_body =
473                callable_target_has_definition_body(analyzer, target);
474            return Some(spec);
475        }
476
477        if target.is_macro() {
478            return Some(Self::new(
479                target.clone(),
480                TargetKind::Macro,
481                None,
482                target.identifier().to_string(),
483                None,
484                None,
485            ));
486        }
487
488        None
489    }
490
491    pub fn with_visible_callable_arities<'a>(
492        &'a self,
493        analyzer: &CppGraphSource<'_>,
494        cpp: &dyn CppSource,
495        visibility: &VisibilityIndex<'_>,
496        file: &ProjectFile,
497        prepared: &PreparedSyntaxTree,
498    ) -> Cow<'a, Self> {
499        let macro_parameter_arity =
500            visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
501        let activated_callable_arities =
502            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
503        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
504            return Cow::Borrowed(self);
505        }
506        let mut effective = self.clone();
507        if let Some(macro_parameter_arity) = macro_parameter_arity {
508            effective.callable_arity = Some(macro_parameter_arity);
509        }
510        effective.activated_callable_arities = activated_callable_arities;
511        Cow::Owned(effective)
512    }
513
514    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
515        let base = self.callable_arity?;
516        Some(
517            self.activated_callable_arities
518                .iter()
519                .filter(|candidate| candidate.activation_byte <= byte)
520                .fold(base, |arity, candidate| {
521                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
522                }),
523        )
524    }
525
526    pub fn new(
527        target: CodeUnit,
528        kind: TargetKind,
529        owner: Option<CodeUnit>,
530        member_name: String,
531        callable_arity: Option<CallableArity>,
532        param_types: Option<Vec<String>>,
533    ) -> Self {
534        Self {
535            target,
536            kind,
537            owner,
538            member_name,
539            callable_arity,
540            activated_callable_arities: Vec::new(),
541            param_types,
542            enum_owner_kind: EnumOwnerKind::NonEnum,
543            owner_is_forward_declaration: false,
544            callable_has_definition_body: false,
545        }
546    }
547}
548
549fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
550    let Some(cpp) = analyzer.cpp else {
551        return false;
552    };
553    let Some(prepared) = cpp.prepared_syntax(analyzer.token, target.source()) else {
554        return false;
555    };
556    analyzer.ranges(target).into_iter().any(|range| {
557        let end = range
558            .start_byte
559            .saturating_add(1)
560            .min(prepared.source().len());
561        let mut current = prepared
562            .tree()
563            .root_node()
564            .descendant_for_byte_range(range.start_byte, end);
565        while let Some(node) = current {
566            match node.kind() {
567                "function_definition" => return true,
568                "declaration" => return false,
569                _ => current = node.parent(),
570            }
571        }
572        false
573    })
574}
575
576fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
577    LogicalSymbolKey {
578        kind: unit.kind(),
579        fq_name: unit.fq_name(),
580        signature: unit.signature().map(str::to_string),
581    }
582}
583
584fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
585    let classify = |source: &str| {
586        let source = source.trim_start();
587        if source.starts_with("enum class ") || source.starts_with("enum struct ") {
588            Some(EnumOwnerKind::Scoped)
589        } else if source.starts_with("enum ") {
590            Some(EnumOwnerKind::Unscoped)
591        } else {
592            None
593        }
594    };
595    owner
596        .signature()
597        .and_then(classify)
598        .or_else(|| {
599            analyzer
600                .get_source(owner, false)
601                .as_deref()
602                .and_then(classify)
603        })
604        .unwrap_or(EnumOwnerKind::NonEnum)
605}
606
607#[derive(Clone, PartialEq, Eq, Hash)]
608pub struct CppScanBinding {
609    pub unit: Option<CodeUnit>,
610    pub type_name: Option<String>,
611    pub indirection: i32,
612}
613
614impl CppScanBinding {
615    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
616        Self {
617            type_name: Some(cpp_name_for(&unit)),
618            unit: Some(unit),
619            indirection,
620        }
621    }
622
623    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
624        Self {
625            type_name: Some(type_name),
626            unit,
627            indirection,
628        }
629    }
630
631    pub fn as_arg_type(&self) -> Option<CppArgType> {
632        let name = self
633            .type_name
634            .clone()
635            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
636        Some(CppArgType {
637            name,
638            unit: self.unit.clone(),
639            indirection: self.indirection,
640            pointee_const: false,
641        })
642    }
643}
644
645type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
646pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
647pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
648type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
649type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
650type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
651type MacroLexicalTemplateCache =
652    HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
653
654type MacroLocalBindingTemplateCache =
655    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
656type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
657type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
658type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
659
660struct StructuredIncludeFact {
661    start_byte: usize,
662    end_byte: usize,
663    path: String,
664}
665
666#[derive(Clone, Default)]
667pub struct MacroEnvironment {
668    bindings: HashMap<String, MacroBinding>,
669    known_undefined_names: HashSet<String>,
670    /// Names the translation unit's compile command proves defined (#2011):
671    /// the `-D`s that survive command ordering, intersected across every
672    /// configuration naming the TU. Seeded once at TU start. An explicit
673    /// `#undef` seen later lands in `known_undefined_names` and wins.
674    build_proven_defines: HashSet<String>,
675    unknown_names: bool,
676    applied_pragma_once_files: HashSet<ProjectFile>,
677    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
678}
679
680/// How many macro events one checkpoint window may cover.
681///
682/// A request for an environment replays only the events between the nearest
683/// earlier checkpoint and its own frontier, so one file's whole scan costs its
684/// event count (the checkpoint build) plus this many applications per request,
685/// whatever order the requests arrive in. The forward cursor this replaced was
686/// optimal for one worker reading one file in byte order and quadratic for the
687/// inverse, which asks many workers for positions that move backwards (#1496).
688pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
689
690/// One event prefix of a file whose environment the index keeps.
691struct MacroEnvironmentCheckpoint {
692    /// How many of the file's events this environment has applied.
693    frontier: usize,
694    environment: Arc<MacroEnvironment>,
695}
696
697/// The replay checkpoints for one file's macro events, ascending by frontier
698/// and always starting at frontier zero (the compile-proven defines alone).
699///
700/// Checkpoints use an adaptive stride no greater than
701/// [`MACRO_ENVIRONMENT_CHECKPOINT_STRIDE`], and one lands directly after every
702/// `#include` event. Applying an include event replays the included file's
703/// complete event list, so keeping one there holds that unbounded cost out of
704/// every later replay window.
705struct MacroEnvironmentCheckpoints {
706    checkpoints: Vec<MacroEnvironmentCheckpoint>,
707}
708
709impl MacroEnvironmentCheckpoints {
710    /// The latest checkpoint at or before `frontier`.
711    fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
712        let index = self
713            .checkpoints
714            .partition_point(|checkpoint| checkpoint.frontier <= frontier);
715        assert!(
716            index > 0,
717            "a checkpoint vector starts at frontier zero, which precedes every request"
718        );
719        &self.checkpoints[index - 1]
720    }
721}
722
723impl MacroEnvironment {
724    fn binding(&self, name: &str) -> Option<&MacroBinding> {
725        self.bindings.get(name)
726    }
727
728    fn may_bind(&self, name: &str) -> bool {
729        self.bindings.contains_key(name) || self.unknown_names
730    }
731
732    fn insert(&mut self, name: String, binding: MacroBinding) {
733        self.known_undefined_names.remove(&name);
734        self.bindings.insert(name, binding);
735    }
736
737    fn remove(&mut self, name: &str) {
738        self.bindings.remove(name);
739        self.known_undefined_names.insert(name.to_string());
740    }
741
742    fn remove_known_undefined(&mut self, name: &str) {
743        self.known_undefined_names.remove(name);
744    }
745
746    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
747        for binding in self.bindings.values_mut() {
748            *binding = MacroBinding::uncertain_from(binding, source, byte);
749        }
750        self.known_undefined_names.clear();
751        // An untracked include could `#undef` a command-line define, so the
752        // may-hold filter must stop treating the build facts as decisive from
753        // here on. The additive proof path keeps its facts: they still hold at
754        // the include chain's activation point.
755        self.build_proven_defines.clear();
756        self.unknown_names = true;
757    }
758
759    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
760        guards.iter().all(|guard| self.guard_may_hold(guard))
761    }
762
763    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
764        let Some(expression) = guard.as_boolean_expression() else {
765            return true;
766        };
767        self.boolean_guard_may_hold(&expression)
768    }
769
770    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
771        match expression {
772            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
773            BooleanGuardExpression::Undefined(name) => {
774                self.bindings
775                    .get(name)
776                    .is_none_or(|binding| !binding.is_exact())
777                    && (!self.build_proven_defines.contains(name)
778                        || self.known_undefined_names.contains(name))
779            }
780            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
781            BooleanGuardExpression::Opaque(_)
782            | BooleanGuardExpression::NegatedOpaque(_)
783            | BooleanGuardExpression::Constant(true) => true,
784            BooleanGuardExpression::Constant(false) => false,
785            BooleanGuardExpression::All(expressions) => expressions
786                .iter()
787                .all(|expression| self.boolean_guard_may_hold(expression)),
788            BooleanGuardExpression::Any(expressions) => expressions
789                .iter()
790                .any(|expression| self.boolean_guard_may_hold(expression)),
791        }
792    }
793}
794
795#[derive(Clone)]
796pub enum EffectiveUsingTarget {
797    Ordinary {
798        name: String,
799        target_components: Vec<String>,
800        global: bool,
801    },
802    Namespace {
803        namespace_components: Vec<String>,
804        global: bool,
805    },
806}
807
808#[derive(Clone)]
809pub struct OrdinaryTypeImport {
810    pub target: EffectiveUsingTarget,
811    pub source: ProjectFile,
812    pub declaration_byte: usize,
813    pub scope_start: usize,
814    pub scope_end: usize,
815    pub scope_depth: usize,
816    pub block_scope: bool,
817    pub lexical_depth: usize,
818    pub declaration_namespace: Vec<String>,
819    pub namespace_scope: Option<Vec<String>>,
820    pub resolved_target_components: Option<Vec<String>>,
821    pub required_guards: HashSet<PreprocessorGuard>,
822}
823
824#[derive(Clone)]
825pub struct ConditionalIncludeProjection {
826    pub activation_byte: usize,
827    pub required_guards: HashSet<PreprocessorGuard>,
828    /// The subset of `required_guards` a lone `#if` contributes: a branch no
829    /// sibling `#else` completes, so no configuration is obliged to take it.
830    /// [`IncludePathAdmission::Compatible`] still demands those.
831    pub partial_guards: HashSet<PreprocessorGuard>,
832}
833
834#[derive(Default)]
835pub struct SourceUsingIndex {
836    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
837    pub directives: Vec<OrdinaryTypeImport>,
838}
839
840#[derive(Default)]
841pub struct ProjectUsingIndex {
842    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
843    pub directives: Vec<OrdinaryTypeImport>,
844}
845
846type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
847
848pub struct EffectiveUsingIndex {
849    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
850}
851
852impl EffectiveUsingIndex {
853    fn new(_root: ProjectFile) -> Self {
854        Self {
855            projected_by_name: Mutex::new(HashMap::default()),
856        }
857    }
858
859    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
860        self.projected_by_name
861            .lock()
862            .expect("C++ effective-using projection cache poisoned")
863            .entry(name.to_string())
864            .or_default()
865            .clone()
866    }
867}
868
869pub enum OrdinaryTypeImportResolution {
870    Resolved {
871        target: CodeUnit,
872        target_components: Vec<String>,
873        lexical_depth: usize,
874        is_direct: bool,
875    },
876    Ambiguous {
877        lexical_depth: usize,
878    },
879    Missing,
880}
881
882type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
883type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
884type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
885type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
886type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
887type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
888type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
889type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
890
891/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
892/// read from its declaration syntax rather than from its persisted signature
893/// string: the comparable shape of each parameter, and the trailing identity
894/// suffix that shape does not carry.
895struct ExtractedComparable {
896    shapes: Vec<CppComparableSlot>,
897    suffix: String,
898}
899
900/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
901/// before giving up on a written type name. A visited set already stops a
902/// cycle; this stops an adversarially long chain from costing a lookup per hop.
903const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
904
905/// Which comparison a conditional `#include` path's guards face before the
906/// header it reaches counts as visible at a reference.
907///
908/// `Proven` is the build's rule: `compile_commands.json` covers the
909/// reference's translation unit, so the reference's active guards plus the
910/// defines the build proves must imply every guard on the path, and a
911/// platform macro the build does not prove keeps the header invisible
912/// (#2011).
913///
914/// `Compatible` is the rule for a translation unit no build describes. A
915/// complete `#if`/`#else` family is a case analysis every configuration takes
916/// one branch of, so a branch of one is admitted unless its guards contradict
917/// the reference's own -- the rule the resolver already applies to a
918/// declaration's own guards (#2988). A lone `#if` is a different claim: no
919/// configuration is obliged to take it, so its guards stay strictly required
920/// and an unprovable one still reports `missing_compile_context` (#2011).
921///
922/// Without the first half, libuv's `uv/threadpool.h`, which `uv.h` includes
923/// from both arms of `#if defined(_WIN32)`, and `uv/unix.h`, which it includes
924/// from the `#else` arm, are invisible to every unguarded reference in the
925/// project (#3088).
926#[derive(Clone, Copy, Debug, PartialEq, Eq)]
927enum IncludePathAdmission {
928    Proven,
929    Compatible,
930}
931
932impl IncludePathAdmission {
933    fn admits(
934        self,
935        required: &HashSet<PreprocessorGuard>,
936        partial: &HashSet<PreprocessorGuard>,
937        reference_guards: Option<&HashSet<PreprocessorGuard>>,
938    ) -> bool {
939        match self {
940            Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
941            Self::Compatible => {
942                guard_requirements_hold_at_reference(partial, reference_guards)
943                    && guards_compatible_at_reference(required, reference_guards)
944            }
945        }
946    }
947}
948
949/// Per-query C++ visibility facts.
950///
951/// The analyzer is *borrowed*, never cloned: `TreeSitterAnalyzer::clone` gives
952/// the clone a fresh, empty `QueryReadCache` on purpose (clones cross
953/// generations and overlays, where another generation's hydrated states would
954/// be wrong). An index that owned a clone would therefore see an inactive read
955/// cache for every `prepared_syntax` call it makes, re-reading and re-parsing
956/// the same source from the store once per candidate instead of once per query
957/// — the #1175 blow-up, where one scan re-parsed a 4.8 MB generated header
958/// tens of thousands of times.
959pub struct VisibilityIndex<'a> {
960    cpp: &'a dyn CppSource,
961    /// Proof that the request scope the index was built under is still open.
962    /// The index is a per-query object whose lifetime is inside the scope's,
963    /// so carrying the token here instead of on ninety method signatures is
964    /// the same guarantee for far less plumbing (issue #2414 step 3).
965    token: QueryToken<'a>,
966    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
967    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
968    global_field_internal_linkage: HashMap<CodeUnit, bool>,
969    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
970    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
971    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
972    parser_alias_target_matches:
973        RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
974    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
975    project_using_index: OnceLock<ProjectUsingIndex>,
976    callable_reference_specs:
977        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
978    structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
979    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
980    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
981    include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
982    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
983    #[cfg(any(test, feature = "test-support"))]
984    conditional_include_projection_index_build_count: AtomicUsize,
985    #[cfg(any(test, feature = "test-support"))]
986    conditional_include_projection_state_count: AtomicUsize,
987    #[cfg(any(test, feature = "test-support"))]
988    conditional_include_target_state_count: AtomicUsize,
989    #[cfg(any(test, feature = "test-support"))]
990    include_activation_build_count: AtomicUsize,
991    #[cfg(any(test, feature = "test-support"))]
992    using_donor_activation_count: AtomicUsize,
993    #[cfg(any(test, feature = "test-support"))]
994    using_namespace_lookup_count: AtomicUsize,
995    #[cfg(any(test, feature = "test-support"))]
996    using_name_candidate_inspection_count: AtomicUsize,
997    #[cfg(any(test, feature = "test-support"))]
998    callable_reference_spec_build_count: AtomicUsize,
999    #[cfg(any(test, feature = "test-support"))]
1000    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1001    #[cfg(any(test, feature = "test-support"))]
1002    visible_parser_alias_name_set_build_count: AtomicUsize,
1003    parser_alias_fallback_calls: AtomicUsize,
1004    parser_alias_fallback_files: AtomicUsize,
1005    parser_alias_source_parses: AtomicUsize,
1006    parser_alias_fallback_elapsed_micros: AtomicUsize,
1007    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1008    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1009    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1010    comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1011    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1012    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1013    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1014    c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1015    c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1016    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1017    macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1018    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1019    // The environment at selected event prefixes of each file, built once and read by every
1020    // worker. The authoritative differential shares this index across target workers whose
1021    // frontiers interleave arbitrarily and move backwards, so a forward cursor -- per worker or
1022    // not -- replayed a file's events once per backward request. Checkpoints answer any position
1023    // with a binary search and at most one stride of replay, and being immutable they need no
1024    // per-worker copy (#1496).
1025    macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1026    macro_replacements: Mutex<MacroReplacementCache>,
1027    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1028    pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1029    macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1030    macro_type_parameters: Mutex<MacroTypeParameterCache>,
1031    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1032    #[cfg(any(test, feature = "test-support"))]
1033    pub macro_replacement_parse_count: AtomicUsize,
1034    #[cfg(any(test, feature = "test-support"))]
1035    pub macro_event_application_count: AtomicUsize,
1036    /// How many files had their checkpoint vector built. One build per file
1037    /// per query even when workers race for the same file.
1038    #[cfg(any(test, feature = "test-support"))]
1039    pub macro_environment_checkpoint_build_count: AtomicUsize,
1040    /// How many requests landed off a checkpoint and so had to copy one and
1041    /// replay the events after it.
1042    #[cfg(any(test, feature = "test-support"))]
1043    pub macro_environment_copy_count: AtomicUsize,
1044    #[cfg(any(test, feature = "test-support"))]
1045    pub macro_environment_request_count: AtomicUsize,
1046    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1047    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1048    #[cfg(any(test, feature = "test-support"))]
1049    qualified_candidate_inspections: AtomicUsize,
1050    #[cfg(any(test, feature = "test-support"))]
1051    target_preserving_type_resolution_count: AtomicUsize,
1052    #[cfg(any(test, feature = "test-support"))]
1053    visibility_identifier_lookup_count: usize,
1054    #[cfg(any(test, feature = "test-support"))]
1055    visibility_identifier_batch_count: usize,
1056}
1057
1058impl Drop for VisibilityIndex<'_> {
1059    fn drop(&mut self) {
1060        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1061            return;
1062        }
1063        #[cfg(any(test, feature = "test-support"))]
1064        eprintln!(
1065            "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1066            self.macro_environment_request_count.load(Ordering::Relaxed),
1067            self.macro_environment_copy_count.load(Ordering::Relaxed),
1068            self.macro_environment_checkpoint_build_count
1069                .load(Ordering::Relaxed),
1070            self.macro_event_application_count.load(Ordering::Relaxed),
1071        );
1072        let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1073        if calls == 0 {
1074            return;
1075        }
1076        eprintln!(
1077            "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1078            calls,
1079            self.parser_alias_fallback_files.load(Ordering::Relaxed),
1080            self.parser_alias_source_parses.load(Ordering::Relaxed),
1081            self.parser_alias_fallback_elapsed_micros
1082                .load(Ordering::Relaxed)
1083                / 1_000,
1084        );
1085    }
1086}
1087
1088#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1089pub enum PreprocessorGuard {
1090    Defined(String),
1091    Undefined(String),
1092    Boolean(BooleanGuardExpression),
1093    Expression(String),
1094    NegatedExpression(String),
1095    Constant(bool),
1096}
1097
1098#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1099pub enum BooleanGuardExpression {
1100    Defined(String),
1101    Undefined(String),
1102    Truthy(String),
1103    Falsy(String),
1104    Opaque(String),
1105    NegatedOpaque(String),
1106    All(Vec<BooleanGuardExpression>),
1107    Any(Vec<BooleanGuardExpression>),
1108    Constant(bool),
1109}
1110
1111impl BooleanGuardExpression {
1112    fn negated(&self) -> Self {
1113        match self {
1114            Self::Defined(name) => Self::Undefined(name.clone()),
1115            Self::Undefined(name) => Self::Defined(name.clone()),
1116            Self::Truthy(name) => Self::Falsy(name.clone()),
1117            Self::Falsy(name) => Self::Truthy(name.clone()),
1118            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1119            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1120            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1121            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1122            Self::Constant(value) => Self::Constant(!value),
1123        }
1124    }
1125
1126    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1127        Self::normalized(expressions, true)
1128    }
1129
1130    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1131        Self::normalized(expressions, false)
1132    }
1133
1134    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1135        let mut normalized = Vec::new();
1136        for expression in expressions {
1137            match expression {
1138                Self::All(nested) if conjunction => normalized.extend(nested),
1139                Self::Any(nested) if !conjunction => normalized.extend(nested),
1140                Self::Constant(value) if value == conjunction => {}
1141                Self::Constant(value) => return Self::Constant(value),
1142                expression => normalized.push(expression),
1143            }
1144        }
1145        normalized.sort_unstable();
1146        normalized.dedup();
1147        match normalized.len() {
1148            0 => Self::Constant(conjunction),
1149            1 => normalized.pop().expect("one Boolean guard expression"),
1150            _ if conjunction => Self::All(normalized),
1151            _ => Self::Any(normalized),
1152        }
1153    }
1154
1155    fn implies(&self, required: &Self) -> bool {
1156        if self == required
1157            || matches!(self, Self::Constant(false))
1158            || matches!(required, Self::Constant(true))
1159        {
1160            return true;
1161        }
1162        if matches!(
1163            (self, required),
1164            (Self::Truthy(active), Self::Defined(required))
1165                | (Self::Undefined(active), Self::Falsy(required))
1166                if active == required
1167        ) {
1168            return true;
1169        }
1170        match self {
1171            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1172            Self::All(active) => match required {
1173                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1174                _ => active.iter().any(|expression| expression.implies(required)),
1175            },
1176            _ => match required {
1177                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1178                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1179                _ => false,
1180            },
1181        }
1182    }
1183
1184    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1185        match self {
1186            Self::Defined(name)
1187            | Self::Undefined(name)
1188            | Self::Truthy(name)
1189            | Self::Falsy(name) => name == macro_name,
1190            // Opaque expressions have structured conditional ownership but no
1191            // structured macro operands, so any mutation may change them.
1192            Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1193            Self::All(expressions) | Self::Any(expressions) => expressions
1194                .iter()
1195                .any(|expression| expression.may_depend_on_macro(macro_name)),
1196            Self::Constant(_) => false,
1197        }
1198    }
1199
1200    pub fn heap_size(&self) -> usize {
1201        match self {
1202            Self::Defined(value)
1203            | Self::Undefined(value)
1204            | Self::Truthy(value)
1205            | Self::Falsy(value)
1206            | Self::Opaque(value)
1207            | Self::NegatedOpaque(value) => value.len(),
1208            Self::All(expressions) | Self::Any(expressions) => {
1209                expressions
1210                    .iter()
1211                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1212                        size.saturating_add(std::mem::size_of::<Self>())
1213                            .saturating_add(expression.heap_size())
1214                    })
1215            }
1216            Self::Constant(_) => 0,
1217        }
1218    }
1219}
1220
1221impl PreprocessorGuard {
1222    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1223        match self {
1224            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1225            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1226            Self::Boolean(expression) => Some(expression.clone()),
1227            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1228            Self::Expression(_) | Self::NegatedExpression(_) => None,
1229        }
1230    }
1231
1232    fn negated(&self) -> Self {
1233        match self {
1234            Self::Defined(name) => Self::Undefined(name.clone()),
1235            Self::Undefined(name) => Self::Defined(name.clone()),
1236            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1237            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1238            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1239            Self::Constant(value) => Self::Constant(!value),
1240        }
1241    }
1242
1243    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1244        match self {
1245            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1246            Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1247            // These expressions could not be lowered to a Boolean operand
1248            // tree, so their dependencies remain unknown.
1249            Self::Expression(_) | Self::NegatedExpression(_) => true,
1250            Self::Constant(_) => false,
1251        }
1252    }
1253}
1254
1255#[derive(Clone, PartialEq, Eq)]
1256pub enum MacroDefinition {
1257    Object {
1258        replacement: String,
1259    },
1260    Function {
1261        parameters: Vec<String>,
1262        replacement: String,
1263    },
1264    VariadicFunction {
1265        parameters: Vec<String>,
1266        replacement: String,
1267    },
1268    Unsupported,
1269}
1270
1271#[derive(Clone, Debug, PartialEq, Eq)]
1272pub enum MacroIncludeProtection {
1273    MacroGuard(String),
1274    PragmaOnce,
1275    None,
1276}
1277
1278enum ParsedMacroReplacement {
1279    Parsed { source: String, tree: Tree },
1280    Unsupported,
1281}
1282
1283/// The sentinel that gives a function-like macro replacement a parseable
1284/// statement context. The replacement text is copied in verbatim, so the only
1285/// bytes ahead of it are this prefix.
1286const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1287
1288/// A function-like macro replacement parsed inside a sentinel function body.
1289///
1290/// Tree-sitter keeps a `#define NAME(a) ...` replacement as one opaque
1291/// `preproc_arg`. Wrapping that exact byte slice in a function body recovers
1292/// its statements, declarations, and member calls as ordinary C++ structure.
1293/// The slice is copied verbatim at [`Self::body_offset`], so a node range in
1294/// [`Self::tree`] maps back onto the defining `preproc_arg` by subtracting
1295/// that offset.
1296pub struct ParsedReplacementBody {
1297    pub source: String,
1298    pub tree: Tree,
1299    pub body_offset: usize,
1300    pub parameters: Vec<String>,
1301    /// For a normal parse this is an identity map. A constrained statement
1302    /// recovery may insert separators after a macro formal; each boundary in
1303    /// this map points back to the corresponding byte in the original
1304    /// replacement, keeping source-backed ranges exact after substitution.
1305    original_offsets: Box<[usize]>,
1306}
1307
1308impl ParsedReplacementBody {
1309    /// The sentinel function body holding the replacement's statements.
1310    pub fn statements(&self) -> Option<Node<'_>> {
1311        first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1312            .child_by_field_name("body")
1313    }
1314
1315    /// The byte range `node` occupies in the file that defines the macro.
1316    ///
1317    /// `replacement_start` is the logical replacement span's start byte.
1318    /// The origin map accounts for continuation whitespace and any synthetic
1319    /// statement separators inserted while parsing the replacement.
1320    pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1321        assert!(
1322            node.start_byte() >= self.body_offset,
1323            "synthetic sentinel node cannot be mapped to a macro replacement"
1324        );
1325        assert!(
1326            node.end_byte() >= self.body_offset,
1327            "synthetic sentinel node cannot be mapped to a macro replacement"
1328        );
1329        let start_offset = node.start_byte() - self.body_offset;
1330        let end_offset = node.end_byte() - self.body_offset;
1331        assert!(start_offset <= end_offset);
1332        let start_origin = *self
1333            .original_offsets
1334            .get(start_offset)
1335            .expect("replacement node start must have a source mapping");
1336        let end_origin = *self
1337            .original_offsets
1338            .get(end_offset)
1339            .expect("replacement node end must have a source mapping");
1340        assert!(start_origin <= end_origin);
1341        let start = replacement_start + start_origin;
1342        let end = replacement_start + end_origin;
1343        start..end
1344    }
1345
1346    /// Whether the replacement names the variadic argument pack.
1347    ///
1348    /// `__VA_ARGS__` parses as an ordinary identifier, so the sentinel tree
1349    /// gives no error for it even though the expansion it stands for is
1350    /// unknown at the definition. Reject it from the parsed tree rather than
1351    /// by scanning the replacement text.
1352    fn expands_variadic_arguments(&self) -> bool {
1353        let mut stack = vec![self.tree.root_node()];
1354        while let Some(node) = stack.pop() {
1355            if matches!(
1356                node.kind(),
1357                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1358            ) && node_text(node, &self.source) == "__VA_ARGS__"
1359            {
1360                return true;
1361            }
1362            push_named_children_reversed(node, &mut stack);
1363        }
1364        false
1365    }
1366}
1367
1368fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1369    let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1370    let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1371        if compact.starts_with("0x") || compact.starts_with("0X") {
1372            (16, 2, |ch| ch.is_ascii_hexdigit())
1373        } else if compact.starts_with("0b") || compact.starts_with("0B") {
1374            (2, 2, |ch| matches!(ch, '0' | '1'))
1375        } else if compact.starts_with('0') && compact.len() > 1 {
1376            (8, 0, |ch| matches!(ch, '0'..='7'))
1377        } else {
1378            (10, 0, |ch| ch.is_ascii_digit())
1379        };
1380    let digit_len = compact[digits_start..]
1381        .chars()
1382        .take_while(|ch| digit_matches(*ch))
1383        .map(char::len_utf8)
1384        .sum::<usize>();
1385    if digit_len == 0 {
1386        return None;
1387    }
1388    let digits_end = digits_start + digit_len;
1389    if !compact[digits_end..]
1390        .chars()
1391        .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1392    {
1393        return None;
1394    }
1395    i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1396}
1397
1398#[derive(Clone)]
1399enum MacroLocalBindingTypeTemplate {
1400    Parameter(usize),
1401    Fixed(String),
1402}
1403
1404#[derive(Clone)]
1405struct MacroLocalBindingTemplate {
1406    name: String,
1407    declared_type: MacroLocalBindingTypeTemplate,
1408    pointer_depth: i32,
1409}
1410
1411/// A local declaration contributed by one structurally known function-like macro.
1412///
1413/// `type_node` points into the invocation syntax when the replacement's type
1414/// is one of the macro parameters. Consumers can therefore use their normal
1415/// lexical type resolver without parsing replacement text themselves.
1416/// `proven_unit` carries a structured C-tag resolution when the grammar splits
1417/// an explicit `struct` or `union` argument across recovery nodes.
1418pub struct MacroLocalBinding<'tree> {
1419    pub name: String,
1420    pub type_name: String,
1421    pub type_node: Option<Node<'tree>>,
1422    pub pointer_depth: i32,
1423    pub proven_unit: Option<CodeUnit>,
1424}
1425
1426/// The source-backed declaration introduced by a function-like macro. The
1427/// declaration belongs to the macro definition, while the reference range
1428/// returned by [`VisibilityIndex::macro_lexical_references`] belongs to the
1429/// definition body, invocation argument, or caller scope where substitution
1430/// makes the binding visible.
1431#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1432pub enum MacroLexicalBindingKind {
1433    Parameter,
1434    Local,
1435}
1436
1437#[derive(Clone, Debug, PartialEq, Eq)]
1438pub struct MacroLexicalBinding {
1439    pub definition: ProjectFile,
1440    pub kind: MacroLexicalBindingKind,
1441    pub name: String,
1442    pub name_range: std::ops::Range<usize>,
1443    pub declaration_range: std::ops::Range<usize>,
1444}
1445
1446/// Lexical macro references are query-local facts. A cancelled or bounded
1447/// walk is kept distinct from a complete empty enumeration so authoritative
1448/// inverse callers can preserve their completeness status.
1449#[derive(Clone, Debug, Default, PartialEq, Eq)]
1450pub struct MacroLexicalReferences {
1451    pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1452    pub truncated: bool,
1453    pub cancelled: bool,
1454}
1455
1456fn macro_replacement_type_parameters(
1457    body: &ParsedReplacementBody,
1458    parameters: &[String],
1459) -> Option<Vec<usize>> {
1460    let mut found = Vec::new();
1461    let mut stack = vec![body.tree.root_node()];
1462    while let Some(node) = stack.pop() {
1463        let type_position = node.kind() == "type_identifier"
1464            || (node.kind() == "identifier"
1465                && node.parent().is_some_and(|parent| {
1466                    parent.kind() == "type_descriptor"
1467                        && parent.child_by_field_name("type") == Some(node)
1468                }));
1469        let offsetof_type_position = node.kind() == "identifier"
1470            && node
1471                .parent()
1472                .filter(|parent| parent.kind() == "argument_list")
1473                .and_then(|arguments| arguments.parent())
1474                .is_some_and(|call| {
1475                    call.kind() == "call_expression"
1476                        && call
1477                            .child_by_field_name("function")
1478                            .is_some_and(|function| {
1479                                function.kind() == "identifier"
1480                                    && node_text(function, &body.source) == "offsetof"
1481                            })
1482                        && call
1483                            .child_by_field_name("arguments")
1484                            .is_some_and(|arguments| {
1485                                argument_children(arguments).next() == Some(node)
1486                            })
1487                });
1488        if (type_position || offsetof_type_position)
1489            && let Some(index) = parameters
1490                .iter()
1491                .position(|parameter| parameter == node_text(node, &body.source))
1492            && !found.contains(&index)
1493        {
1494            found.push(index);
1495        }
1496        push_named_children_reversed(node, &mut stack);
1497    }
1498    (!found.is_empty()).then_some(found)
1499}
1500
1501fn macro_replacement_type_parameter(
1502    body: &ParsedReplacementBody,
1503    parameters: &[String],
1504) -> Option<usize> {
1505    let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1506    (parameters.len() == 1).then(|| parameters.pop().unwrap())
1507}
1508
1509pub(crate) fn macro_type_argument_node<'tree>(
1510    node: Node<'tree>,
1511    source: &str,
1512) -> Option<Node<'tree>> {
1513    match node.kind() {
1514        "type_descriptor" => {
1515            let type_child = node
1516                .child_by_field_name("type")
1517                .or_else(|| first_type_child(node))?;
1518            for index in (0..node.named_child_count()).rev() {
1519                let child = node.named_child(index)?;
1520                if child != type_child
1521                    && matches!(
1522                        child.kind(),
1523                        "identifier"
1524                            | "type_identifier"
1525                            | "qualified_identifier"
1526                            | "scoped_type_identifier"
1527                    )
1528                {
1529                    return Some(child);
1530                }
1531            }
1532            macro_type_argument_node(type_child, source)
1533        }
1534        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1535            node.child_by_field_name("name")
1536        }
1537        "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1538            .next_named_sibling()
1539            .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1540            .and_then(|error| error.named_child(0))
1541            .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1542        _ => cpp_name_component_nodes(node).is_some().then_some(node),
1543    }
1544}
1545
1546fn c_function_macro_argument<'tree>(
1547    node: Node<'tree>,
1548) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1549    let mut current = node;
1550    while let Some(parent) = current.parent() {
1551        if parent.kind() == "argument_list" {
1552            let call = parent.parent().filter(|call| {
1553                call.kind() == "call_expression"
1554                    && call.child_by_field_name("arguments") == Some(parent)
1555                    && call
1556                        .child_by_field_name("function")
1557                        .is_some_and(|function| {
1558                            function.kind() == "identifier"
1559                                && !node_range_contains(function, current)
1560                        })
1561            })?;
1562            let mut actuals = argument_children(parent).enumerate();
1563            let (index, argument) = actuals.find(|(_, argument)| {
1564                node_range_contains(*argument, node)
1565                    && (argument.start_byte() == current.start_byte()
1566                        || argument.end_byte() == current.end_byte())
1567            })?;
1568            return Some((call, index, argument));
1569        }
1570        current = parent;
1571    }
1572    None
1573}
1574
1575/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1576/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1577/// invocation as the assignment's left operand and the declared name as one
1578/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1579fn recognized_c_macro_declarator_binding<'tree>(
1580    statement: Node<'tree>,
1581    source: &str,
1582) -> Option<MacroLocalBinding<'tree>> {
1583    let assignment = match statement.kind() {
1584        "assignment_expression" => statement,
1585        "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1586        _ => return None,
1587    };
1588    if assignment.kind() != "assignment_expression" {
1589        return None;
1590    }
1591    let call = assignment.child_by_field_name("left")?;
1592    if call.kind() != "call_expression" {
1593        return None;
1594    }
1595    let function = call.child_by_field_name("function")?;
1596    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1597        return None;
1598    }
1599    let arguments = call.child_by_field_name("arguments")?;
1600    let mut actuals = argument_children(arguments);
1601    let type_node = actuals.next()?;
1602    if actuals.next().is_some()
1603        || !matches!(
1604            type_node.kind(),
1605            "identifier"
1606                | "type_identifier"
1607                | "qualified_identifier"
1608                | "scoped_type_identifier"
1609                | "template_type"
1610        )
1611    {
1612        return None;
1613    }
1614    let name_node = (0..assignment.named_child_count())
1615        .filter_map(|index| assignment.named_child(index))
1616        .filter(|child| child.kind() == "ERROR")
1617        .filter_map(|error| {
1618            (error.named_child_count() == 1)
1619                .then(|| error.named_child(0))
1620                .flatten()
1621        })
1622        .find(|node| node.kind() == "identifier")?;
1623    let name = node_text(name_node, source).trim();
1624    let type_name = node_text(type_node, source).trim();
1625    if name.is_empty() || type_name.is_empty() {
1626        return None;
1627    }
1628    Some(MacroLocalBinding {
1629        name: name.to_string(),
1630        type_name: type_name.to_string(),
1631        type_node: Some(type_node),
1632        pointer_depth: 1,
1633        proven_unit: None,
1634    })
1635}
1636
1637#[derive(Clone, PartialEq, Eq)]
1638pub struct MacroBinding {
1639    source: ProjectFile,
1640    declaration_byte: usize,
1641    definition: MacroDefinition,
1642    exact: bool,
1643}
1644
1645impl MacroBinding {
1646    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1647        Self {
1648            source: source.clone(),
1649            declaration_byte,
1650            definition: MacroDefinition::Unsupported,
1651            exact: false,
1652        }
1653    }
1654
1655    fn is_exact(&self) -> bool {
1656        self.exact
1657    }
1658
1659    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1660        Self {
1661            source: source.clone(),
1662            declaration_byte,
1663            definition: current.definition.clone(),
1664            exact: false,
1665        }
1666    }
1667}
1668
1669/// The preprocessor conditionals whose truth decides whether one macro event
1670/// applies, by the start byte of each conditional node. Empty means the event
1671/// is unconditional.
1672///
1673/// [`VisibilityIndex::macro_event_condition_value`] needs exactly the
1674/// conditional ancestors that structurally contain the event, and deciding
1675/// containment means asking [`cpp_displaced_preprocessor_boundary`] for an
1676/// `#endif` tree-sitter displaced into error recovery -- a walk of the
1677/// conditional's whole subtree. That answer is a fact about the tree alone, so
1678/// it is settled once, when the events are collected, instead of on every
1679/// replay of the event (#1496).
1680type OwningPreprocessorConditionals = Box<[usize]>;
1681
1682#[derive(Clone)]
1683pub enum MacroEvent {
1684    Define {
1685        name: String,
1686        binding: MacroBinding,
1687        byte: usize,
1688        conditionals: OwningPreprocessorConditionals,
1689    },
1690    Undef {
1691        name: String,
1692        byte: usize,
1693        conditionals: OwningPreprocessorConditionals,
1694    },
1695    Include {
1696        targets: Vec<ProjectFile>,
1697        byte: usize,
1698        conditionals: OwningPreprocessorConditionals,
1699    },
1700    Invalidate {
1701        byte: usize,
1702    },
1703}
1704
1705impl MacroEvent {
1706    pub fn byte(&self) -> usize {
1707        match self {
1708            Self::Define { byte, .. }
1709            | Self::Undef { byte, .. }
1710            | Self::Include { byte, .. }
1711            | Self::Invalidate { byte } => *byte,
1712        }
1713    }
1714}
1715
1716#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1717pub enum CallArityEvidence {
1718    Exact(usize),
1719    Unknown,
1720}
1721
1722impl CallArityEvidence {
1723    pub fn exact(self) -> Option<usize> {
1724        match self {
1725            Self::Exact(arity) => Some(arity),
1726            Self::Unknown => None,
1727        }
1728    }
1729
1730    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1731        self.exact().map(|arity| expected.accepts(arity))
1732    }
1733}
1734
1735#[derive(Clone)]
1736struct DeclaredFieldTypeFact {
1737    type_text: String,
1738    indirection: i32,
1739    /// The member binds `type_text` through a pointer or a reference, so it
1740    /// holds no subobject of that type (see [`DeclaredNameBinding`]).
1741    binds_indirectly: bool,
1742    template_arguments: Option<Vec<CppTemplateExpression>>,
1743}
1744
1745#[derive(Clone, PartialEq, Eq)]
1746enum StructuredAliasTarget {
1747    Builtin,
1748    Named {
1749        components: Vec<String>,
1750        global: bool,
1751        arguments: Option<Vec<CppTemplateExpression>>,
1752    },
1753}
1754
1755struct CppAlias {
1756    name: String,
1757    target: String,
1758    namespace: Option<String>,
1759}
1760
1761type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1762
1763/// Why template-argument resolution failed. Definition diagnostics render
1764/// each mode differently; graph scans only care that the resolution is
1765/// unproven and match `Err(_)`.
1766#[derive(Debug, Clone, PartialEq, Eq)]
1767pub enum CppTemplateResolutionError {
1768    /// A template alias expansion revisited `alias`.
1769    AliasCycle { alias: CodeUnit },
1770    /// The explicit arguments do not bind to the declared template parameters.
1771    ArgumentBinding,
1772    /// Bound arguments do not substitute into the alias target's arguments.
1773    Substitution,
1774    /// No visible primary template declaration could be selected and
1775    /// reconciled for the specialization family.
1776    PrimarySelection,
1777    /// More than one applicable specialization remains and none is strictly
1778    /// more specialized than every other candidate.
1779    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1780}
1781
1782/// The ambiguity candidates, deduplicated to one representative per visible
1783/// symbol so a diagnostic lists each contender once.
1784fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1785    let mut distinct: Vec<CodeUnit> = Vec::new();
1786    for unit in units {
1787        if !distinct
1788            .iter()
1789            .any(|existing| same_visible_symbol(existing, unit))
1790        {
1791            distinct.push(unit.clone());
1792        }
1793    }
1794    distinct
1795}
1796
1797/// Enumerate macro lexical references while constructing visibility only if
1798/// the bounded source walk finds a definition or identifier call candidate.
1799pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1800    visibility: Factory,
1801    file: &ProjectFile,
1802    root: Node<'_>,
1803    source: &str,
1804    max_references: usize,
1805    cancelled: impl FnMut() -> bool,
1806) -> MacroLexicalReferences
1807where
1808    Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1809{
1810    crate::graph::macro_lexical::all_references(
1811        visibility,
1812        file,
1813        root,
1814        source,
1815        max_references,
1816        cancelled,
1817    )
1818}
1819
1820impl<'a> VisibilityIndex<'a> {
1821    pub fn cpp(&self) -> &'a dyn CppSource {
1822        self.cpp
1823    }
1824
1825    /// The request-scope proof this index was built with (issue #2414 step 3).
1826    pub fn token(&self) -> QueryToken<'a> {
1827        self.token
1828    }
1829
1830    /// Whether `file` has a structured unresolved include that is active before
1831    /// the reference at `before_byte`.
1832    ///
1833    /// Include facts are collected from the prepared tree once per visibility
1834    /// query. The reference position is still evaluated for each call because
1835    /// preprocessor visibility depends on the reference's own conditional
1836    /// context.
1837    pub fn has_unresolved_include_visible_before(
1838        &self,
1839        file: &ProjectFile,
1840        before_byte: usize,
1841    ) -> bool {
1842        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
1843            return false;
1844        };
1845        let cell = self
1846            .structured_include_fact_cells
1847            .lock()
1848            .expect("C++ structured include-fact cache poisoned")
1849            .entry(file.clone())
1850            .or_default()
1851            .clone();
1852        let facts = cell.get_or_init(|| collect_structured_include_facts(prepared.as_ref()));
1853        has_unresolved_include_visible_before_in_prepared(
1854            file,
1855            prepared.as_ref(),
1856            self.cpp.include_target_index(),
1857            facts,
1858            before_byte,
1859        )
1860    }
1861
1862    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1863    /// bypassing the include-closure walk [`Self::build`] performs.
1864    ///
1865    /// The resolver's own unit tests drive the type-resolution paths against a
1866    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1867    /// because they need a real `CppAnalyzer`, so the struct literal they used
1868    /// to write inline is here instead of thirty-three public fields.
1869    #[cfg(any(test, feature = "test-support"))]
1870    pub fn from_visible_files_for_test(
1871        cpp: &'a dyn CppSource,
1872        token: QueryToken<'a>,
1873        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1874    ) -> Self {
1875        let visible_source_files_by_root = visible_by_file
1876            .iter()
1877            .map(|(file, visible)| {
1878                (
1879                    file.clone(),
1880                    visible
1881                        .iter()
1882                        .map(|unit| unit.source().clone())
1883                        .chain(std::iter::once(file.clone()))
1884                        .collect(),
1885                )
1886            })
1887            .collect();
1888        let mut global_field_internal_linkage = HashMap::default();
1889        Self {
1890            cpp,
1891            token,
1892            visible_by_identifier: build_visible_identifier_index(
1893                &CppGraphSource::from_source(cpp, token),
1894                &visible_by_file,
1895                &visible_source_files_by_root,
1896                &mut global_field_internal_linkage,
1897            ),
1898            global_field_internal_linkage,
1899            visible_by_file,
1900            visible_source_files_by_root,
1901            alias_cells: Mutex::new(HashMap::default()),
1902            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1903            parser_alias_target_matches: RwLock::new(HashMap::default()),
1904            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1905            project_using_index: OnceLock::new(),
1906            callable_reference_specs: Mutex::new(HashMap::default()),
1907            structured_include_fact_cells: Mutex::new(HashMap::default()),
1908            include_activation_cells: Mutex::new(HashMap::default()),
1909            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1910            include_path_admission_cells: Mutex::new(HashMap::default()),
1911            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1912            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1913            conditional_include_projection_state_count: AtomicUsize::new(0),
1914            conditional_include_target_state_count: AtomicUsize::new(0),
1915            include_activation_build_count: AtomicUsize::new(0),
1916            using_donor_activation_count: AtomicUsize::new(0),
1917            using_namespace_lookup_count: AtomicUsize::new(0),
1918            using_name_candidate_inspection_count: AtomicUsize::new(0),
1919            callable_reference_spec_build_count: AtomicUsize::new(0),
1920            alias_source_parse_counts: Mutex::new(HashMap::default()),
1921            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1922            parser_alias_fallback_calls: AtomicUsize::new(0),
1923            parser_alias_fallback_files: AtomicUsize::new(0),
1924            parser_alias_source_parses: AtomicUsize::new(0),
1925            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1926            field_type_facts: Mutex::new(HashMap::default()),
1927            structured_alias_targets: Mutex::new(HashMap::default()),
1928            callable_comparables: Mutex::new(HashMap::default()),
1929            comparable_name_declarations: Mutex::new(HashMap::default()),
1930            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1931            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1932            precise_parent_cache: Mutex::new(HashMap::default()),
1933            c_tag_kind_cache: Mutex::new(HashMap::default()),
1934            c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
1935            macro_event_cells: Mutex::new(HashMap::default()),
1936            macro_event_name_sets: Mutex::new(HashMap::default()),
1937            macro_include_protection_cells: Mutex::new(HashMap::default()),
1938            macro_environment_checkpoints: Mutex::new(HashMap::default()),
1939            macro_replacements: Mutex::new(HashMap::default()),
1940            macro_local_binding_templates: Mutex::new(HashMap::default()),
1941            macro_lexical_templates: Mutex::new(HashMap::default()),
1942            macro_replacement_bodies: Mutex::new(HashMap::default()),
1943            macro_type_parameters: Mutex::new(HashMap::default()),
1944            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1945            macro_replacement_parse_count: AtomicUsize::new(0),
1946            macro_event_application_count: AtomicUsize::new(0),
1947            macro_environment_checkpoint_build_count: AtomicUsize::new(0),
1948            macro_environment_copy_count: AtomicUsize::new(0),
1949            macro_environment_request_count: AtomicUsize::new(0),
1950            cpp_template_metadata: HashMap::default(),
1951            cpp_template_families: HashMap::default(),
1952            qualified_candidate_inspections: AtomicUsize::new(0),
1953            target_preserving_type_resolution_count: AtomicUsize::new(0),
1954            visibility_identifier_lookup_count: 0,
1955            visibility_identifier_batch_count: 0,
1956        }
1957    }
1958
1959    /// The index's own C++ source, in the dispatching-analyzer shape.
1960    ///
1961    /// Four resolution paths reach the workspace through the C++ analyzer they
1962    /// already hold rather than through the analyzer the query was issued
1963    /// against; before the move they passed `&CppAnalyzer` straight into a
1964    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
1965    fn cpp_source(&self) -> CppGraphSource<'a> {
1966        CppGraphSource::from_source(self.cpp, self.token)
1967    }
1968
1969    pub fn build(
1970        cpp: &'a dyn CppSource,
1971        token: QueryToken<'a>,
1972        analyzer: &CppGraphSource<'_>,
1973        roots: &HashSet<ProjectFile>,
1974    ) -> Self {
1975        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1976    }
1977
1978    pub fn build_with_cancellation(
1979        cpp: &'a dyn CppSource,
1980        token: QueryToken<'a>,
1981        analyzer: &CppGraphSource<'_>,
1982        roots: &HashSet<ProjectFile>,
1983        cancellation: Option<&CancellationToken>,
1984    ) -> Self {
1985        let visibility_started = Instant::now();
1986        let include_targets = cpp.include_target_index();
1987        let includes_started = Instant::now();
1988        let mut include_graph = IncludeGraph::default();
1989        for root in roots {
1990            include_graph.extend_with(root, cancellation, &mut |file| {
1991                cpp_include_paths(&cpp.visibility_import_statements(token, file))
1992                    .into_iter()
1993                    .flat_map(|include| {
1994                        resolve_include_targets_with_index(file, &include, include_targets)
1995                    })
1996                    .collect()
1997            });
1998        }
1999        let include_elapsed = includes_started.elapsed();
2000        let include_file_count = include_graph.files().count();
2001        let visible_source_files_by_root = roots
2002            .iter()
2003            .map(|root| {
2004                (
2005                    root.clone(),
2006                    include_graph.reachable_files(root, cancellation),
2007                )
2008            })
2009            .collect::<HashMap<_, _>>();
2010        let mut visibility_stats = BoundedVisibilityStats::default();
2011        let mut visible_by_file = build_bounded_visible_declarations(
2012            cpp,
2013            token,
2014            analyzer,
2015            roots,
2016            &visible_source_files_by_root,
2017            cancellation,
2018            &mut visibility_stats,
2019        );
2020        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2021            eprintln!(
2022                "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_ast_nodes={} dependency_names={} lookup_ms={} declaration_ms={} dependency_ast_ms={}",
2023                visibility_started.elapsed().as_millis(),
2024                include_elapsed.as_millis(),
2025                include_file_count,
2026                visibility_stats.rounds,
2027                visibility_stats.root_names,
2028                visibility_stats.identifier_lookups,
2029                visibility_stats.identifier_batches,
2030                visibility_stats.candidate_units,
2031                visibility_stats.candidate_sources,
2032                visibility_stats.declaration_reads,
2033                visibility_stats.declaration_units,
2034                visibility_stats.selected_units,
2035                visibility_stats.dependency_ast_nodes,
2036                visibility_stats.dependency_names,
2037                visibility_stats.lookup_elapsed.as_millis(),
2038                visibility_stats.declaration_elapsed.as_millis(),
2039                visibility_stats.dependency_ast_elapsed.as_millis(),
2040            );
2041        }
2042        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2043        let finalize_started = Instant::now();
2044        if report_stats {
2045            eprintln!(
2046                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2047                visible_by_file.len(),
2048                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2049            );
2050        }
2051        let owner_started = Instant::now();
2052        if report_stats {
2053            eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2054        }
2055        let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2056        if report_stats {
2057            eprintln!(
2058                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2059                owner_stats.unseen_owners,
2060                owner_stats.definition_lookups,
2061                owner_stats.admitted,
2062                owner_started.elapsed().as_millis(),
2063            );
2064        }
2065        let mut global_field_internal_linkage = HashMap::default();
2066        let identifier_started = Instant::now();
2067        if report_stats {
2068            eprintln!(
2069                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2070            );
2071        }
2072        let visible_by_identifier = build_visible_identifier_index(
2073            analyzer,
2074            &visible_by_file,
2075            &visible_source_files_by_root,
2076            &mut global_field_internal_linkage,
2077        );
2078        if report_stats {
2079            eprintln!(
2080                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2081                visible_by_identifier.len(),
2082                visible_by_identifier
2083                    .values()
2084                    .map(HashMap::len)
2085                    .sum::<usize>(),
2086                visible_by_identifier
2087                    .values()
2088                    .flat_map(HashMap::values)
2089                    .map(Vec::len)
2090                    .sum::<usize>(),
2091                identifier_started.elapsed().as_millis(),
2092            );
2093        }
2094        let mut cpp_template_metadata = HashMap::default();
2095        let metadata_started = Instant::now();
2096        let mut template_classes = 0usize;
2097        if report_stats {
2098            eprintln!(
2099                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2100            );
2101        }
2102        for unit in visible_by_file
2103            .values()
2104            .flatten()
2105            .filter(|unit| unit.is_class())
2106        {
2107            template_classes += 1;
2108            if cpp_template_metadata.contains_key(unit) {
2109                continue;
2110            }
2111            if let Some(metadata) = cpp.template_metadata(unit) {
2112                cpp_template_metadata.insert(unit.clone(), metadata);
2113            }
2114        }
2115        if report_stats {
2116            eprintln!(
2117                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2118                template_classes,
2119                cpp_template_metadata.len(),
2120                metadata_started.elapsed().as_millis(),
2121            );
2122        }
2123        let families_started = Instant::now();
2124        if report_stats {
2125            eprintln!(
2126                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2127            );
2128        }
2129        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2130        for (unit, metadata) in &cpp_template_metadata {
2131            cpp_template_families
2132                .entry(metadata.primary_fq_name.clone())
2133                .or_default()
2134                .push(unit.clone());
2135        }
2136        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
2137        // order above is a function of those hashes. Two mirrored headers can
2138        // declare one specialization; `select_template_specialization` treats
2139        // them as interchangeable and returns the family's first entry, so an
2140        // unsorted family made the reported declaration depend on the
2141        // workspace's absolute path and on unrelated files (#1836). Order the
2142        // family exactly as `build_visible_identifier_index` orders its
2143        // per-identifier candidate lists.
2144        for family in cpp_template_families.values_mut() {
2145            sort_lookup_units(family);
2146        }
2147        if report_stats {
2148            eprintln!(
2149                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2150                cpp_template_families.len(),
2151                cpp_template_families.values().map(Vec::len).sum::<usize>(),
2152                families_started.elapsed().as_millis(),
2153            );
2154            eprintln!(
2155                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2156                visible_by_file.len(),
2157                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2158                finalize_started.elapsed().as_millis(),
2159                visibility_started.elapsed().as_millis(),
2160            );
2161        }
2162        Self {
2163            cpp,
2164            token,
2165            visible_by_file,
2166            visible_by_identifier,
2167            global_field_internal_linkage,
2168            visible_source_files_by_root,
2169            alias_cells: Mutex::new(HashMap::default()),
2170            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2171            parser_alias_target_matches: RwLock::new(HashMap::default()),
2172            ordinary_type_import_cells: Mutex::new(HashMap::default()),
2173            project_using_index: OnceLock::new(),
2174            callable_reference_specs: Mutex::new(HashMap::default()),
2175            structured_include_fact_cells: Mutex::new(HashMap::default()),
2176            include_activation_cells: Mutex::new(HashMap::default()),
2177            compile_proven_guard_cells: Mutex::new(HashMap::default()),
2178            include_path_admission_cells: Mutex::new(HashMap::default()),
2179            conditional_include_projection_cells: Mutex::new(HashMap::default()),
2180            #[cfg(any(test, feature = "test-support"))]
2181            conditional_include_projection_index_build_count: AtomicUsize::new(0),
2182            #[cfg(any(test, feature = "test-support"))]
2183            conditional_include_projection_state_count: AtomicUsize::new(0),
2184            #[cfg(any(test, feature = "test-support"))]
2185            conditional_include_target_state_count: AtomicUsize::new(0),
2186            #[cfg(any(test, feature = "test-support"))]
2187            include_activation_build_count: AtomicUsize::new(0),
2188            #[cfg(any(test, feature = "test-support"))]
2189            using_donor_activation_count: AtomicUsize::new(0),
2190            #[cfg(any(test, feature = "test-support"))]
2191            using_namespace_lookup_count: AtomicUsize::new(0),
2192            #[cfg(any(test, feature = "test-support"))]
2193            using_name_candidate_inspection_count: AtomicUsize::new(0),
2194            #[cfg(any(test, feature = "test-support"))]
2195            callable_reference_spec_build_count: AtomicUsize::new(0),
2196            #[cfg(any(test, feature = "test-support"))]
2197            alias_source_parse_counts: Mutex::new(HashMap::default()),
2198            #[cfg(any(test, feature = "test-support"))]
2199            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2200            parser_alias_fallback_calls: AtomicUsize::new(0),
2201            parser_alias_fallback_files: AtomicUsize::new(0),
2202            parser_alias_source_parses: AtomicUsize::new(0),
2203            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2204            field_type_facts: Mutex::new(HashMap::default()),
2205            structured_alias_targets: Mutex::new(HashMap::default()),
2206            callable_comparables: Mutex::new(HashMap::default()),
2207            comparable_name_declarations: Mutex::new(HashMap::default()),
2208            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2209            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2210            precise_parent_cache: Mutex::new(HashMap::default()),
2211            c_tag_kind_cache: Mutex::new(HashMap::default()),
2212            c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2213            macro_event_cells: Mutex::new(HashMap::default()),
2214            macro_event_name_sets: Mutex::new(HashMap::default()),
2215            macro_include_protection_cells: Mutex::new(HashMap::default()),
2216            macro_environment_checkpoints: Mutex::new(HashMap::default()),
2217            macro_replacements: Mutex::new(HashMap::default()),
2218            macro_local_binding_templates: Mutex::new(HashMap::default()),
2219            macro_lexical_templates: Mutex::new(HashMap::default()),
2220            macro_replacement_bodies: Mutex::new(HashMap::default()),
2221            macro_type_parameters: Mutex::new(HashMap::default()),
2222            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2223            #[cfg(any(test, feature = "test-support"))]
2224            macro_replacement_parse_count: AtomicUsize::new(0),
2225            #[cfg(any(test, feature = "test-support"))]
2226            macro_event_application_count: AtomicUsize::new(0),
2227            #[cfg(any(test, feature = "test-support"))]
2228            macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2229            #[cfg(any(test, feature = "test-support"))]
2230            macro_environment_copy_count: AtomicUsize::new(0),
2231            #[cfg(any(test, feature = "test-support"))]
2232            macro_environment_request_count: AtomicUsize::new(0),
2233            cpp_template_metadata,
2234            cpp_template_families,
2235            #[cfg(any(test, feature = "test-support"))]
2236            qualified_candidate_inspections: AtomicUsize::new(0),
2237            #[cfg(any(test, feature = "test-support"))]
2238            target_preserving_type_resolution_count: AtomicUsize::new(0),
2239            #[cfg(any(test, feature = "test-support"))]
2240            visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2241            #[cfg(any(test, feature = "test-support"))]
2242            visibility_identifier_batch_count: visibility_stats.identifier_batches,
2243        }
2244    }
2245
2246    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2247        if file == target.source() {
2248            return true;
2249        }
2250        if self.global_field_has_internal_linkage(target) {
2251            return self
2252                .visible_source_files_by_root
2253                .get(file)
2254                .is_some_and(|sources| sources.contains(target.source()));
2255        }
2256        self.visible_by_file
2257            .get(file)
2258            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2259    }
2260
2261    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2262        self.global_field_internal_linkage
2263            .get(unit)
2264            .copied()
2265            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2266    }
2267
2268    pub fn call_arity_evidence(
2269        &self,
2270        file: &ProjectFile,
2271        call: Node<'_>,
2272        source: &str,
2273    ) -> CallArityEvidence {
2274        self.call_arity_evidence_at(file, call, source, call.start_byte())
2275    }
2276
2277    /// Argument-count evidence for a call whose macro environment is not the
2278    /// one at its own byte offset.
2279    ///
2280    /// A call recovered from a macro replacement lives in a sentinel parse of
2281    /// its own, so its node offsets say nothing about which macros are active.
2282    /// `environment_byte` names the position in `file` whose macro environment
2283    /// governs the call: the macro definition site for a replacement body.
2284    pub fn call_arity_evidence_at(
2285        &self,
2286        file: &ProjectFile,
2287        call: Node<'_>,
2288        source: &str,
2289        environment_byte: usize,
2290    ) -> CallArityEvidence {
2291        let Some(arguments) = call
2292            .child_by_field_name("arguments")
2293            .or_else(|| call.child_by_field_name("parameters"))
2294            .or_else(|| call.child_by_field_name("value"))
2295            .or_else(|| first_named_child_of_kind(call, "argument_list"))
2296            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2297        else {
2298            return CallArityEvidence::Exact(0);
2299        };
2300        let recovered_c_keyword_arguments =
2301            recovered_c_keyword_argument_count(file, call, arguments, source);
2302        let c_semantics = reference_uses_c_semantics(self.cpp, file);
2303        let arguments = argument_children(arguments)
2304            .flat_map(|argument| {
2305                recovered_c_new_expression_arguments(argument, c_semantics)
2306                    .map(Vec::from)
2307                    .unwrap_or_else(|| vec![argument])
2308            })
2309            .collect::<Vec<_>>();
2310        if arguments
2311            .iter()
2312            .all(|argument| !argument_shape_may_change_arity(*argument))
2313        {
2314            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2315        }
2316        let environment = self.macro_environment(file, environment_byte);
2317        let mut stack = Vec::new();
2318        let mut total = recovered_c_keyword_arguments;
2319        for argument in arguments {
2320            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2321                return CallArityEvidence::Unknown;
2322            }
2323            let CallArityEvidence::Exact(spread) =
2324                self.argument_arity_evidence(argument, source, &environment, &mut stack)
2325            else {
2326                return CallArityEvidence::Unknown;
2327            };
2328            total += spread;
2329        }
2330        CallArityEvidence::Exact(total)
2331    }
2332
2333    fn argument_arity_evidence(
2334        &self,
2335        argument: Node<'_>,
2336        source: &str,
2337        environment: &MacroEnvironment,
2338        stack: &mut Vec<(ProjectFile, usize)>,
2339    ) -> CallArityEvidence {
2340        let (name, invocation_arguments, function_like) = match argument.kind() {
2341            "identifier" => (node_text(argument, source), None, false),
2342            "call_expression" => {
2343                let Some(function) = argument.child_by_field_name("function") else {
2344                    return CallArityEvidence::Exact(1);
2345                };
2346                if function.kind() != "identifier" {
2347                    return CallArityEvidence::Exact(1);
2348                }
2349                let Some(arguments) = argument.child_by_field_name("arguments") else {
2350                    return CallArityEvidence::Exact(1);
2351                };
2352                (node_text(function, source), Some(arguments), true)
2353            }
2354            _ => return CallArityEvidence::Exact(1),
2355        };
2356        let Some(binding) = environment.binding(name) else {
2357            return if environment.unknown_names {
2358                CallArityEvidence::Unknown
2359            } else {
2360                CallArityEvidence::Exact(1)
2361            };
2362        };
2363        if !binding.is_exact() {
2364            return CallArityEvidence::Unknown;
2365        }
2366        match (&binding.definition, invocation_arguments, function_like) {
2367            (MacroDefinition::Object { replacement }, None, false) => self
2368                .replacement_arity_evidence(
2369                    replacement,
2370                    &[],
2371                    &[],
2372                    source,
2373                    environment,
2374                    stack,
2375                    binding,
2376                ),
2377            (
2378                MacroDefinition::Function {
2379                    parameters,
2380                    replacement,
2381                },
2382                Some(arguments),
2383                true,
2384            ) => {
2385                let actuals = argument_children(arguments).collect::<Vec<_>>();
2386                if actuals.len() != parameters.len() {
2387                    CallArityEvidence::Unknown
2388                } else {
2389                    self.replacement_arity_evidence(
2390                        replacement,
2391                        parameters,
2392                        &actuals,
2393                        source,
2394                        environment,
2395                        stack,
2396                        binding,
2397                    )
2398                }
2399            }
2400            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2401            _ => CallArityEvidence::Unknown,
2402        }
2403    }
2404
2405    #[allow(clippy::too_many_arguments)]
2406    fn replacement_arity_evidence(
2407        &self,
2408        replacement: &str,
2409        parameters: &[String],
2410        actuals: &[Node<'_>],
2411        actual_source: &str,
2412        environment: &MacroEnvironment,
2413        stack: &mut Vec<(ProjectFile, usize)>,
2414        binding: &MacroBinding,
2415    ) -> CallArityEvidence {
2416        let identity = (binding.source.clone(), binding.declaration_byte);
2417        if stack.contains(&identity) || replacement.trim().is_empty() {
2418            return CallArityEvidence::Unknown;
2419        }
2420        stack.push(identity);
2421        let parsed = self.parsed_macro_replacement(binding, replacement);
2422        let evidence = (|| {
2423            let ParsedMacroReplacement::Parsed {
2424                source: sentinel,
2425                tree,
2426            } = parsed.as_ref()
2427            else {
2428                return None;
2429            };
2430            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2431            let arguments = call.child_by_field_name("arguments")?;
2432            let mut total = 0usize;
2433            for argument in argument_children(arguments) {
2434                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2435                    return None;
2436                }
2437                if argument.kind() == "identifier"
2438                    && let Some(parameter_index) = parameters
2439                        .iter()
2440                        .position(|parameter| parameter == node_text(argument, sentinel))
2441                {
2442                    if !macro_expansion_shape_is_safe(
2443                        actuals[parameter_index],
2444                        actual_source,
2445                        &[],
2446                        environment,
2447                    ) {
2448                        return None;
2449                    }
2450                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2451                        actuals[parameter_index],
2452                        actual_source,
2453                        environment,
2454                        stack,
2455                    ) else {
2456                        return None;
2457                    };
2458                    total += spread;
2459                    continue;
2460                }
2461                let CallArityEvidence::Exact(spread) =
2462                    self.argument_arity_evidence(argument, sentinel, environment, stack)
2463                else {
2464                    return None;
2465                };
2466                total += spread;
2467            }
2468            Some(CallArityEvidence::Exact(total))
2469        })()
2470        .unwrap_or(CallArityEvidence::Unknown);
2471        stack.pop();
2472        evidence
2473    }
2474
2475    fn parsed_macro_replacement(
2476        &self,
2477        binding: &MacroBinding,
2478        replacement: &str,
2479    ) -> Arc<ParsedMacroReplacement> {
2480        let key = (binding.source.clone(), binding.declaration_byte);
2481        let mut cache = self
2482            .macro_replacements
2483            .lock()
2484            .expect("C++ macro replacement cache poisoned");
2485        if let Some(parsed) = cache.get(&key) {
2486            return Arc::clone(parsed);
2487        }
2488        #[cfg(any(test, feature = "test-support"))]
2489        self.macro_replacement_parse_count
2490            .fetch_add(1, Ordering::Relaxed);
2491        let source =
2492            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2493        let mut parser = Parser::new();
2494        let parsed = parser
2495            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2496            .ok()
2497            .and_then(|()| parser.parse(&source, None))
2498            .filter(|tree| !tree.root_node().has_error())
2499            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2500                ParsedMacroReplacement::Parsed { source, tree }
2501            });
2502        let parsed = Arc::new(parsed);
2503        cache.insert(key, Arc::clone(&parsed));
2504        parsed
2505    }
2506
2507    /// Recover a typed local declared by an active C function-like macro.
2508    ///
2509    /// This is intentionally narrower than macro expansion. The replacement
2510    /// must parse as one declaration, and the invocation must bind every
2511    /// formal parameter to one structured argument. That is sufficient for
2512    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
2513    /// can make the binding provisional without erasing its last known
2514    /// definition; an explicit conflicting definition still replaces it with
2515    /// Unsupported. Malformed and statement-producing macros also fail closed.
2516    pub fn function_macro_local_binding<'tree>(
2517        &self,
2518        file: &ProjectFile,
2519        statement: Node<'tree>,
2520        source: &str,
2521    ) -> Option<MacroLocalBinding<'tree>> {
2522        if !is_c_source_file(file) {
2523            return None;
2524        }
2525        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2526            return Some(binding);
2527        }
2528        let call = match statement.kind() {
2529            "call_expression" => statement,
2530            "expression_statement" if statement.named_child_count() == 1 => {
2531                statement.named_child(0)?
2532            }
2533            _ => return None,
2534        };
2535        if call.kind() != "call_expression" {
2536            return None;
2537        }
2538        let function = call.child_by_field_name("function")?;
2539        if function.kind() != "identifier" {
2540            return None;
2541        }
2542        let arguments = call.child_by_field_name("arguments")?;
2543        let actuals = argument_children(arguments).collect::<Vec<_>>();
2544        let environment = self.macro_environment(file, call.start_byte());
2545        let function_name = node_text(function, source);
2546        let binding = environment.binding(function_name)?;
2547        let MacroDefinition::Function {
2548            parameters,
2549            replacement,
2550        } = &binding.definition
2551        else {
2552            return None;
2553        };
2554        if actuals.len() != parameters.len() {
2555            return None;
2556        }
2557        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2558        let (type_name, type_node) = match &template.declared_type {
2559            MacroLocalBindingTypeTemplate::Parameter(index) => {
2560                let actual = *actuals.get(*index)?;
2561                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2562                    return None;
2563                }
2564                (node_text(actual, source).trim().to_string(), Some(actual))
2565            }
2566            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2567        };
2568        if type_name.is_empty() {
2569            return None;
2570        }
2571        Some(MacroLocalBinding {
2572            name: template.name.clone(),
2573            type_name,
2574            type_node,
2575            pointer_depth: template.pointer_depth,
2576            proven_unit: None,
2577        })
2578    }
2579
2580    /// Recover the typed receiver established by a C container macro
2581    /// assignment, such as `value = container_of(ptr, struct item, link)`.
2582    ///
2583    /// The macro definition's parsed replacement identifies the one formal
2584    /// parameter used in type position.  The invocation supplies the actual
2585    /// type node, which is then resolved through the ordinary visibility
2586    /// index.  Calls with no unique type-position parameter, an unresolved
2587    /// type, or an uncertain macro environment remain unproven.
2588    pub fn function_macro_container_binding<'tree>(
2589        &self,
2590        analyzer: &CppGraphSource<'_>,
2591        file: &ProjectFile,
2592        assignment: Node<'tree>,
2593        source: &str,
2594    ) -> Option<MacroLocalBinding<'tree>> {
2595        if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2596            return None;
2597        }
2598        let name_node = assignment.child_by_field_name("left")?;
2599        if name_node.kind() != "identifier" {
2600            return None;
2601        }
2602        let call = assignment.child_by_field_name("right")?;
2603        if call.kind() != "call_expression" {
2604            return None;
2605        }
2606        let function = call.child_by_field_name("function")?;
2607        if function.kind() != "identifier" {
2608            return None;
2609        }
2610        let arguments = call.child_by_field_name("arguments")?;
2611        let actuals = argument_children(arguments).collect::<Vec<_>>();
2612        let function_name = node_text(function, source);
2613        let environment = self.macro_environment(file, call.start_byte());
2614        let binding = environment.binding(function_name)?;
2615        if !binding.is_exact() {
2616            return None;
2617        }
2618        let MacroDefinition::Function {
2619            parameters,
2620            replacement,
2621        } = &binding.definition
2622        else {
2623            return None;
2624        };
2625        if actuals.len() != parameters.len() {
2626            return None;
2627        }
2628        let body = self.parsed_macro_replacement_body(
2629            &(binding.source.clone(), binding.declaration_byte),
2630            parameters,
2631            replacement,
2632        )?;
2633        let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2634        let type_argument = *actuals.get(type_parameter)?;
2635        let type_node = macro_type_argument_node(type_argument, source)?;
2636        let type_name = node_text(type_node, source).trim().to_string();
2637        if type_name.is_empty() {
2638            return None;
2639        }
2640        let explicit_tag = match node_text(type_argument, source) {
2641            "struct" => Some(CppCTagKind::Struct),
2642            "union" => Some(CppCTagKind::Union),
2643            _ => None,
2644        };
2645        let resolved_type = if let Some(tag) = explicit_tag {
2646            let candidates = self
2647                .visible_identifier_candidates(file, &type_name)
2648                .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2649                .collect::<Vec<_>>();
2650            self.resolve_type_candidates(
2651                analyzer,
2652                file,
2653                &candidates,
2654                TypeCandidateResolution::Canonical,
2655            )
2656            .ok()
2657        } else {
2658            self.resolve_type_node_result(file, type_node, source)
2659                .ok()
2660                .flatten()
2661        };
2662        let proven_unit = resolved_type?;
2663        Some(MacroLocalBinding {
2664            name: node_text(name_node, source).to_string(),
2665            type_name,
2666            type_node: Some(type_node),
2667            pointer_depth: 1,
2668            proven_unit: Some(proven_unit),
2669        })
2670    }
2671
2672    /// Recover the invocation-specific type of a replacement local visible
2673    /// at the selected source token.
2674    pub fn macro_local_binding_at<'tree>(
2675        &self,
2676        file: &ProjectFile,
2677        root: Node<'tree>,
2678        source: &str,
2679        start_byte: usize,
2680        end_byte: usize,
2681    ) -> Option<MacroLocalBinding<'tree>> {
2682        crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2683    }
2684
2685    /// Resolve the source-backed lexical macro declaration for one source
2686    /// range. The range may be a point range (`start..start + 1`) inside an
2687    /// identifier, which is the form used by goto-definition queries.
2688    pub fn macro_lexical_binding(
2689        &self,
2690        file: &ProjectFile,
2691        root: Node<'_>,
2692        source: &str,
2693        start_byte: usize,
2694        end_byte: usize,
2695    ) -> Option<MacroLexicalBinding> {
2696        crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2697    }
2698
2699    /// Enumerate source ranges whose lexical meaning comes from a
2700    /// function-like macro formal or replacement local. Definition spellings
2701    /// themselves are omitted; callers needing goto-definition on a spelling
2702    /// use [`Self::macro_lexical_binding`].
2703    pub fn macro_lexical_references(
2704        &self,
2705        file: &ProjectFile,
2706        root: Node<'_>,
2707        source: &str,
2708        max_references: usize,
2709        cancelled: impl FnMut() -> bool,
2710    ) -> MacroLexicalReferences {
2711        crate::graph::macro_lexical::all_references(
2712            || self,
2713            file,
2714            root,
2715            source,
2716            max_references,
2717            cancelled,
2718        )
2719    }
2720
2721    /// Return the source identity of the exact active function-like macro
2722    /// binding. The definition AST is hydrated by the lexical helper from
2723    /// this `(ProjectFile, declaration byte)` pair, so no parser node escapes
2724    /// the prepared source borrow.
2725    pub(crate) fn function_macro_binding_at(
2726        &self,
2727        file: &ProjectFile,
2728        name: &str,
2729        before_byte: usize,
2730    ) -> Option<(ProjectFile, usize)> {
2731        let environment = self.macro_environment(file, before_byte);
2732        let binding = environment.binding(name)?;
2733        if binding.is_exact()
2734            && matches!(
2735                binding.definition,
2736                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2737            )
2738        {
2739            return Some((binding.source.clone(), binding.declaration_byte));
2740        }
2741        // A definition under an unknown condition is still certain at a use
2742        // within that same branch. The checkpoint retains the event that made
2743        // the binding uncertain. Require that exact defining event, so an
2744        // intervening undef, include, or conditional redefinition cannot be
2745        // bypassed by a lexical search for an older macro.
2746        if binding.source != *file {
2747            return None;
2748        }
2749        let prepared = self.cpp.prepared_syntax(self.token, file)?;
2750        let root = prepared.tree().root_node();
2751        let source = prepared.source();
2752        let reference = root.descendant_for_byte_range(
2753            before_byte,
2754            before_byte.saturating_add(1).min(source.len()),
2755        )?;
2756        let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2757        let cell = self.macro_event_cell(file);
2758        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2759        let event = events
2760            .iter()
2761            .find(|event| event.byte() == binding.declaration_byte)?;
2762        let MacroEvent::Define {
2763            name: defined_name,
2764            binding: definition,
2765            conditionals,
2766            ..
2767        } = event
2768        else {
2769            return None;
2770        };
2771        if defined_name != name
2772            || conditionals.is_empty()
2773            || !conditionals
2774                .iter()
2775                .all(|condition| reference_conditions.contains(condition))
2776            || !matches!(
2777                definition.definition,
2778                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2779            )
2780        {
2781            return None;
2782        }
2783        Some((definition.source.clone(), definition.declaration_byte))
2784    }
2785
2786    /// Return the type argument at a C function-like macro invocation.
2787    ///
2788    /// Exact local macro definitions identify type formals from their parsed
2789    /// replacement. An unavailable definition is deliberately not inferred:
2790    /// C has no AST distinction between an external macro call and an
2791    /// ordinary call to an unindexed function.
2792    pub fn function_macro_type_argument<'tree>(
2793        &self,
2794        file: &ProjectFile,
2795        node: Node<'tree>,
2796        source: &str,
2797    ) -> Option<Node<'tree>> {
2798        if !is_c_source_file(file) {
2799            return None;
2800        }
2801        let (call, argument_index, argument) = c_function_macro_argument(node)?;
2802        let function = call.child_by_field_name("function")?;
2803        let function_name = node_text(function, source);
2804        if function_name.is_empty() {
2805            return None;
2806        }
2807        if !self.file_defines_macro_name(file, function_name)
2808            && !self
2809                .visible_identifier_candidates(file, function_name)
2810                .any(|candidate| candidate.is_macro())
2811        {
2812            return None;
2813        }
2814        let environment = self.macro_environment(file, call.start_byte());
2815        let binding = environment.binding(function_name)?;
2816        if !binding.is_exact() {
2817            return None;
2818        }
2819        let (parameters, replacement, variadic) = match &binding.definition {
2820            MacroDefinition::Function {
2821                parameters,
2822                replacement,
2823            } => (parameters, replacement, false),
2824            MacroDefinition::VariadicFunction {
2825                parameters,
2826                replacement,
2827            } => (parameters, replacement, true),
2828            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2829        };
2830        let actuals = call
2831            .child_by_field_name("arguments")
2832            .map(argument_children)
2833            .into_iter()
2834            .flatten()
2835            .collect::<Vec<_>>();
2836        let arity_matches = if variadic {
2837            actuals.len() >= parameters.len()
2838        } else {
2839            actuals.len() == parameters.len()
2840        };
2841        let type_parameters = self.macro_type_parameter_indices(
2842            &(binding.source.clone(), binding.declaration_byte),
2843            parameters,
2844            replacement,
2845        )?;
2846        if !arity_matches || !type_parameters.contains(&argument_index) {
2847            return None;
2848        }
2849        let type_node = macro_type_argument_node(argument, source)?;
2850        if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
2851            return None;
2852        }
2853        Some(type_node)
2854    }
2855
2856    fn macro_local_binding_template(
2857        &self,
2858        binding: &MacroBinding,
2859        parameters: &[String],
2860        replacement: &str,
2861    ) -> Option<Arc<MacroLocalBindingTemplate>> {
2862        let key = (binding.source.clone(), binding.declaration_byte);
2863        if let Some(template) = self
2864            .macro_local_binding_templates
2865            .lock()
2866            .expect("C++ macro local-binding cache poisoned")
2867            .get(&key)
2868        {
2869            return template.clone();
2870        }
2871        let template = (|| {
2872            let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2873            let sentinel = body.source.as_str();
2874            let statements = body.statements()?;
2875            if statements.named_child_count() != 1 {
2876                return None;
2877            }
2878            let declaration = statements.named_child(0)?;
2879            if declaration.kind() != "declaration" {
2880                return None;
2881            }
2882            let type_node = declaration
2883                .child_by_field_name("type")
2884                .or_else(|| first_type_child(declaration))?;
2885            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2886                let mut cursor = declaration.walk();
2887                declaration.named_children(&mut cursor).find_map(|child| {
2888                    if child.kind() == "init_declarator" {
2889                        child.child_by_field_name("declarator")
2890                    } else {
2891                        is_declarator_node(child).then_some(child)
2892                    }
2893                })
2894            })?;
2895            let name = extract_variable_name(declarator, sentinel)?;
2896            let pointer_depth =
2897                declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
2898            let type_text = node_text(type_node, sentinel).trim();
2899            let declared_type = parameters
2900                .iter()
2901                .position(|parameter| parameter == type_text)
2902                .map(MacroLocalBindingTypeTemplate::Parameter)
2903                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2904            Some(Arc::new(MacroLocalBindingTemplate {
2905                name,
2906                declared_type,
2907                pointer_depth,
2908            }))
2909        })();
2910        self.macro_local_binding_templates
2911            .lock()
2912            .expect("C++ macro local-binding cache poisoned")
2913            .insert(key, template.clone());
2914        template
2915    }
2916
2917    /// The parsed replacement body of the function-like macro `definition`
2918    /// defines, or `None` when the replacement cannot be recovered exactly.
2919    ///
2920    /// `definition` is the defining `preproc_function_def` node in `file`, so
2921    /// the result describes that definition rather than whichever same-named
2922    /// macro a later reference resolves to.
2923    pub fn function_macro_replacement_body(
2924        &self,
2925        file: &ProjectFile,
2926        definition: Node<'_>,
2927        source: &str,
2928    ) -> Option<Arc<ParsedReplacementBody>> {
2929        debug_assert_eq!(definition.kind(), "preproc_function_def");
2930        let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
2931            MacroDefinition::Function {
2932                parameters,
2933                replacement,
2934            }
2935            | MacroDefinition::VariadicFunction {
2936                parameters,
2937                replacement,
2938            } => (parameters, replacement),
2939            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2940        };
2941        self.parsed_macro_replacement_body(
2942            &(file.clone(), definition.start_byte()),
2943            &parameters,
2944            &replacement,
2945        )
2946    }
2947
2948    /// Parse one function-like macro replacement inside the shared sentinel.
2949    ///
2950    /// The parse fails closed, and the failure is cached, whenever the
2951    /// sentinel tree carries an error or the replacement uses preprocessor
2952    /// syntax that has no C++ meaning. Token pasting and stringizing produce
2953    /// `ERROR` nodes; `__VA_ARGS__` parses as an ordinary identifier and is
2954    /// therefore rejected from the parsed tree instead of the source text.
2955    fn parsed_macro_replacement_body(
2956        &self,
2957        key: &(ProjectFile, usize),
2958        parameters: &[String],
2959        replacement: &str,
2960    ) -> Option<Arc<ParsedReplacementBody>> {
2961        if let Some(body) = self
2962            .macro_replacement_bodies
2963            .lock()
2964            .expect("C++ macro replacement body cache poisoned")
2965            .get(key)
2966        {
2967            return body.clone();
2968        }
2969        let body = (|| {
2970            if replacement.trim().is_empty() {
2971                return None;
2972            }
2973            let (source, tree, original_offsets) =
2974                Self::parse_macro_replacement_body(replacement, parameters)?;
2975            let body = ParsedReplacementBody {
2976                source,
2977                tree,
2978                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
2979                parameters: parameters.to_vec(),
2980                original_offsets,
2981            };
2982            body.statements()?;
2983            if body.expands_variadic_arguments() {
2984                return None;
2985            }
2986            Some(Arc::new(body))
2987        })();
2988        self.macro_replacement_bodies
2989            .lock()
2990            .expect("C++ macro replacement body cache poisoned")
2991            .insert(key.clone(), body.clone());
2992        body
2993    }
2994
2995    /// Parse a replacement while preserving a map from the parse buffer back
2996    /// to the original logical-line bytes. A block formal can occur where C
2997    /// requires a statement terminator (`if (condition) block`); the caller's
2998    /// actual block supplies that terminator only after macro substitution.
2999    /// Tree-sitter exposes the formal as an identifier inside recovery, so a
3000    /// semicolon is inserted at that AST-derived boundary for the fallback
3001    /// parse. No identifier text is scanned to find the insertion point.
3002    fn parse_macro_replacement_body(
3003        replacement: &str,
3004        parameters: &[String],
3005    ) -> Option<(String, Tree, Box<[usize]>)> {
3006        let normalized = normalize_macro_continuations(replacement);
3007        let parse = |replacement: &str| {
3008            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3009            let mut parser = Parser::new();
3010            parser
3011                .set_language(&tree_sitter_cpp::LANGUAGE.into())
3012                .ok()?;
3013            let tree = parser.parse(&source, None)?;
3014            Some((source, tree))
3015        };
3016        let (source, tree) = parse(&normalized)?;
3017        if !tree.root_node().has_error() {
3018            let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
3019            Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3020            return Some((source, tree, original_offsets.into_boxed_slice()));
3021        }
3022
3023        let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3024        let mut insertion_points = Vec::new();
3025        let mut stack = vec![tree.root_node()];
3026        while let Some(node) = stack.pop() {
3027            if matches!(
3028                node.kind(),
3029                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3030            ) && parameters
3031                .iter()
3032                .any(|parameter| parameter.as_str() == node_text(node, &source))
3033                && Self::macro_formal_needs_statement_separator(node)
3034            {
3035                let point = node.end_byte().saturating_sub(body_offset);
3036                if point <= normalized.len() && !insertion_points.contains(&point) {
3037                    insertion_points.push(point);
3038                }
3039            }
3040            push_named_children_reversed(node, &mut stack);
3041        }
3042        if insertion_points.is_empty() {
3043            return None;
3044        }
3045        insertion_points.sort_unstable();
3046        let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3047        let mut original_offsets =
3048            Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3049        let mut next_insertion = 0;
3050        for (index, byte) in normalized.bytes().enumerate() {
3051            recovered.push(byte);
3052            original_offsets.push(index);
3053            while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3054                recovered.push(b';');
3055                original_offsets.push(index + 1);
3056                next_insertion += 1;
3057            }
3058        }
3059        original_offsets.push(normalized.len());
3060        Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3061        let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3062        let (source, tree) = parse(&recovered)?;
3063        if tree.root_node().has_error() {
3064            return None;
3065        }
3066        Some((source, tree, original_offsets.into_boxed_slice()))
3067    }
3068
3069    /// Extend a replacement-origin map over the `; }` suffix appended by the
3070    /// sentinel parser. Nodes such as a declaration whose source omits its
3071    /// terminator include that synthetic semicolon; every suffix boundary
3072    /// still maps to the replacement's real end.
3073    fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3074        offsets.extend([replacement_end; 3]);
3075    }
3076
3077    /// A formal is statement-shaped when the recovered tree places it directly
3078    /// at a statement boundary. This is the grammar recovery produced for
3079    /// replacement forms such as `if (condition) block`; an identifier inside
3080    /// an expression or call argument must not receive an artificial `;`.
3081    fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3082        let mut current = node;
3083        let mut crossed_recovery = false;
3084        while let Some(parent) = current.parent() {
3085            if parent.is_error() {
3086                crossed_recovery = true;
3087                current = parent;
3088                continue;
3089            }
3090            if matches!(
3091                parent.kind(),
3092                "call_expression"
3093                    | "argument_list"
3094                    | "field_expression"
3095                    | "binary_expression"
3096                    | "unary_expression"
3097                    | "assignment_expression"
3098                    | "conditional_expression"
3099                    | "parenthesized_expression"
3100                    | "subscript_expression"
3101            ) {
3102                return false;
3103            }
3104            if parent.kind() == "expression_statement" {
3105                return parent.named_child_count() == 1;
3106            }
3107            if parent.kind() == "compound_statement" && current == node {
3108                return true;
3109            }
3110            if matches!(
3111                parent.kind(),
3112                "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3113            ) {
3114                return crossed_recovery
3115                    || parent.child_by_field_name("consequence") == Some(current);
3116            }
3117            if matches!(parent.kind(), "declaration" | "init_declarator") {
3118                return false;
3119            }
3120            current = parent;
3121        }
3122        false
3123    }
3124
3125    /// Identify fixed macro formals that retain an unambiguous type role even
3126    /// when an unrelated part of the replacement needs parser recovery.
3127    ///
3128    /// Variadic production macros commonly contain token pasting elsewhere in
3129    /// the body. The strict shared replacement parser must keep rejecting that
3130    /// body for expansion modeling, but a type declaration such as `TY col`
3131    /// remains usable when the node itself retains a structured type role.
3132    fn macro_type_parameter_indices(
3133        &self,
3134        key: &(ProjectFile, usize),
3135        parameters: &[String],
3136        replacement: &str,
3137    ) -> Option<Arc<[usize]>> {
3138        if let Some(indices) = self
3139            .macro_type_parameters
3140            .lock()
3141            .expect("C++ macro type-parameter cache poisoned")
3142            .get(key)
3143        {
3144            return indices.clone();
3145        }
3146        #[cfg(any(test, feature = "test-support"))]
3147        self.macro_replacement_parse_count
3148            .fetch_add(1, Ordering::Relaxed);
3149        let indices = (|| {
3150            if replacement.trim().is_empty() {
3151                return None;
3152            }
3153            // Keep this parser permissive. Type-role extraction predates the
3154            // replacement-body model and deliberately survives unrelated
3155            // token-pasting or recovery elsewhere in a production macro.
3156            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3157            let mut parser = Parser::new();
3158            parser
3159                .set_language(&tree_sitter_cpp::LANGUAGE.into())
3160                .ok()?;
3161            let tree = parser.parse(&source, None)?;
3162            let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3163            Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3164            let body = ParsedReplacementBody {
3165                source,
3166                tree,
3167                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3168                parameters: parameters.to_vec(),
3169                original_offsets: original_offsets.into_boxed_slice(),
3170            };
3171            macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3172        })();
3173        self.macro_type_parameters
3174            .lock()
3175            .expect("C++ macro type-parameter cache poisoned")
3176            .insert(key.clone(), indices.clone());
3177        indices
3178    }
3179
3180    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3181        let replacement = if node.kind() == "preproc_function_def" {
3182            function_macro_replacement_span(node, source)
3183                .and_then(|span| source.get(span))
3184                .map(str::to_owned)
3185                .or_else(|| {
3186                    node.child_by_field_name("value")
3187                        .map(|value| node_text(value, source).to_string())
3188                })
3189                .unwrap_or_default()
3190        } else {
3191            node.child_by_field_name("value")
3192                .map(|value| node_text(value, source).to_string())
3193                .unwrap_or_default()
3194        };
3195        if node.kind() == "preproc_def" {
3196            return MacroDefinition::Object { replacement };
3197        }
3198        let Some(parameters) = node.child_by_field_name("parameters") else {
3199            return MacroDefinition::Unsupported;
3200        };
3201        let variadic = (0..parameters.child_count()).any(|index| {
3202            parameters
3203                .child(index)
3204                .is_some_and(|child| child.kind() == "...")
3205        });
3206        let parameters = (0..parameters.named_child_count())
3207            .filter_map(|index| parameters.named_child(index))
3208            .map(|parameter| node_text(parameter, source).to_string())
3209            .collect::<Vec<_>>();
3210        if variadic {
3211            MacroDefinition::VariadicFunction {
3212                parameters,
3213                replacement,
3214            }
3215        } else {
3216            MacroDefinition::Function {
3217                parameters,
3218                replacement,
3219            }
3220        }
3221    }
3222
3223    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3224        self.macro_event_cells
3225            .lock()
3226            .expect("C++ macro event cache poisoned")
3227            .entry(file.clone())
3228            .or_default()
3229            .clone()
3230    }
3231
3232    fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3233        if let Some(names) = self
3234            .macro_event_name_sets
3235            .lock()
3236            .expect("C++ macro event-name cache poisoned")
3237            .get(file)
3238            .cloned()
3239        {
3240            return names.contains(name);
3241        }
3242        let cell = self.macro_event_cell(file);
3243        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3244        let names = Arc::new(
3245            events
3246                .iter()
3247                .filter_map(|event| match event {
3248                    MacroEvent::Define { name, .. } => Some(name.clone()),
3249                    MacroEvent::Undef { .. }
3250                    | MacroEvent::Include { .. }
3251                    | MacroEvent::Invalidate { .. } => None,
3252                })
3253                .collect(),
3254        );
3255        self.macro_event_name_sets
3256            .lock()
3257            .expect("C++ macro event-name cache poisoned")
3258            .insert(file.clone(), Arc::clone(&names));
3259        names.contains(name)
3260    }
3261
3262    fn macro_environment_checkpoint_cell(
3263        &self,
3264        file: &ProjectFile,
3265    ) -> MacroEnvironmentCheckpointCell {
3266        self.macro_environment_checkpoints
3267            .lock()
3268            .expect("C++ macro environment checkpoint cache poisoned")
3269            .entry(file.clone())
3270            .or_default()
3271            .clone()
3272    }
3273
3274    /// The environment of `file`'s macro events applied up to `before_byte`.
3275    pub fn macro_environment(
3276        &self,
3277        file: &ProjectFile,
3278        before_byte: usize,
3279    ) -> Arc<MacroEnvironment> {
3280        #[cfg(any(test, feature = "test-support"))]
3281        self.macro_environment_request_count
3282            .fetch_add(1, Ordering::Relaxed);
3283        let cell = self.macro_event_cell(file);
3284        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3285        let frontier = events.partition_point(|event| event.byte() < before_byte);
3286        let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3287        let checkpoints =
3288            checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3289        let checkpoint = checkpoints.at_or_before(frontier);
3290        if checkpoint.frontier == frontier {
3291            return Arc::clone(&checkpoint.environment);
3292        }
3293        #[cfg(any(test, feature = "test-support"))]
3294        self.macro_environment_copy_count
3295            .fetch_add(1, Ordering::Relaxed);
3296        let mut environment = checkpoint.environment.as_ref().clone();
3297        let mut include_stack = HashSet::from_iter([file.clone()]);
3298        for event in &events[checkpoint.frontier..frontier] {
3299            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3300        }
3301        Arc::new(environment)
3302    }
3303
3304    /// Apply `file`'s events once, keeping the environment at the prefixes
3305    /// [`MacroEnvironmentCheckpoints`] describes.
3306    fn build_macro_environment_checkpoints(
3307        &self,
3308        file: &ProjectFile,
3309        events: &[MacroEvent],
3310    ) -> MacroEnvironmentCheckpoints {
3311        #[cfg(any(test, feature = "test-support"))]
3312        self.macro_environment_checkpoint_build_count
3313            .fetch_add(1, Ordering::Relaxed);
3314        // The TU's build-proven defines hold from the first byte (#2011): they
3315        // are facts of the whole compile, so they seed the frontier-zero
3316        // checkpoint. A later explicit #undef event still overrides them
3317        // through `known_undefined_names`.
3318        let mut environment = MacroEnvironment {
3319            build_proven_defines: self
3320                .compile_proven_guards(file)
3321                .iter()
3322                .filter_map(|guard| match guard {
3323                    PreprocessorGuard::Defined(name) => Some(name.clone()),
3324                    _ => None,
3325                })
3326                .collect(),
3327            ..MacroEnvironment::default()
3328        };
3329        let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3330            frontier: 0,
3331            environment: Arc::new(environment.clone()),
3332        }];
3333        // Keep roughly one fixed stride's worth of checkpoints, while taking
3334        // a checkpoint at every event for event sets no larger than the fixed
3335        // stride. Generated C tables commonly have thousands of uses between
3336        // one #define and a trailing #undef; with a fixed stride those
3337        // identical frontier requests would all copy and replay the same
3338        // prefix.
3339        let checkpoint_stride = events
3340            .len()
3341            .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3342            .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3343        let mut include_stack = HashSet::from_iter([file.clone()]);
3344        for (index, event) in events.iter().enumerate() {
3345            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3346            let frontier = index + 1;
3347            if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3348                checkpoints.push(MacroEnvironmentCheckpoint {
3349                    frontier,
3350                    environment: Arc::new(environment.clone()),
3351                });
3352            }
3353        }
3354        MacroEnvironmentCheckpoints { checkpoints }
3355    }
3356
3357    /// Whether `name` is bound as a macro at `before_byte` in `file`,
3358    /// including a binding this environment cannot pin to one replacement
3359    /// (a conditional `#define`, or a function-like macro).
3360    ///
3361    /// [`Self::object_macro_replacement_at`] collapses every such binding to
3362    /// `None`, which is indistinguishable from "not a macro at all". A caller
3363    /// that must not read a macro token as an ordinary type name needs the two
3364    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
3365    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3366        self.macro_environment(file, before_byte)
3367            .binding(name)
3368            .is_some()
3369    }
3370
3371    pub fn macro_name_may_be_bound_at(
3372        &self,
3373        file: &ProjectFile,
3374        name: &str,
3375        before_byte: usize,
3376    ) -> bool {
3377        self.macro_environment(file, before_byte).may_bind(name)
3378    }
3379
3380    /// Whether the active macro binding at this reference is the requested
3381    /// indexed definition. Name equality alone is not enough because two
3382    /// headers can define the same macro for different translation units.
3383    pub fn macro_binding_matches_target_at(
3384        &self,
3385        analyzer: &CppGraphSource<'_>,
3386        file: &ProjectFile,
3387        name: &str,
3388        before_byte: usize,
3389        target: &CodeUnit,
3390    ) -> bool {
3391        let ranges = analyzer.ranges(target);
3392        let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3393        self.macro_binding_matches_target_declaration_at(
3394            file,
3395            name,
3396            before_byte,
3397            target.source(),
3398            &declaration_bytes,
3399        )
3400    }
3401
3402    /// Resolve declaration ranges once for inverse scans that test many call
3403    /// sites against one macro target. Walking a large generated syntax tree
3404    /// back to the same `#define` for every call site is otherwise quadratic
3405    /// in the number of top-level declarations.
3406    pub(crate) fn macro_declaration_bytes(
3407        &self,
3408        target: &CodeUnit,
3409        ranges: &[Range],
3410    ) -> Vec<usize> {
3411        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3412            return Vec::new();
3413        };
3414        ranges
3415            .iter()
3416            .filter_map(|range| {
3417                let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3418                while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3419                    node = node.parent()?;
3420                }
3421                Some(node.start_byte())
3422            })
3423            .collect()
3424    }
3425
3426    pub(crate) fn macro_binding_matches_target_declaration_at(
3427        &self,
3428        file: &ProjectFile,
3429        name: &str,
3430        before_byte: usize,
3431        target_source: &ProjectFile,
3432        target_declaration_bytes: &[usize],
3433    ) -> bool {
3434        let environment = self.macro_environment(file, before_byte);
3435        let Some(binding) = environment.binding(name) else {
3436            return false;
3437        };
3438        if binding.definition == MacroDefinition::Unsupported {
3439            return false;
3440        }
3441        // A normal header guard makes the replacement text conditional, but
3442        // it does not erase the definition site's source and byte identity.
3443        // Keep that identity even when expansion details are not exact.
3444        if binding.source != *target_source {
3445            return false;
3446        }
3447        target_declaration_bytes.contains(&binding.declaration_byte)
3448    }
3449
3450    /// Resolve an ordinary expression-position macro token at its exact byte.
3451    ///
3452    /// Calls and preprocessor-condition tokens have separate resolution
3453    /// surfaces. Declaration names, macro parameters, and labels are not
3454    /// references. Keeping that role policy here makes forward and both
3455    /// inverse graph builders consume the same activation verdict (#2093).
3456    pub fn resolve_ordinary_macro_reference(
3457        &self,
3458        analyzer: &CppGraphSource<'_>,
3459        file: &ProjectFile,
3460        node: Node<'_>,
3461        source: &str,
3462    ) -> OrdinaryMacroReferenceResolution {
3463        if !is_ordinary_macro_reference_node(node) {
3464            return OrdinaryMacroReferenceResolution::Missing;
3465        }
3466        let name = node_text(node, source);
3467        if name.is_empty() {
3468            return OrdinaryMacroReferenceResolution::Missing;
3469        }
3470        let visible = self
3471            .visible_identifier_candidates(file, name)
3472            .filter(|candidate| candidate.is_macro())
3473            .cloned()
3474            .collect::<Vec<_>>();
3475        let mut exact = Vec::new();
3476        for candidate in &visible {
3477            if self.macro_binding_matches_target_at(
3478                analyzer,
3479                file,
3480                name,
3481                node.start_byte(),
3482                candidate,
3483            ) && !exact
3484                .iter()
3485                .any(|existing| same_visible_symbol(existing, candidate))
3486            {
3487                exact.push(candidate.clone());
3488            }
3489        }
3490        match exact.len() {
3491            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3492            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3493            0 if !visible.is_empty()
3494                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3495            {
3496                OrdinaryMacroReferenceResolution::Ambiguous
3497            }
3498            0 => OrdinaryMacroReferenceResolution::Missing,
3499        }
3500    }
3501
3502    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
3503    ///
3504    /// The ordinary census deliberately skips every `ERROR` subtree. This
3505    /// separate, precision-only frontier admits only roles that retain enough
3506    /// structure for the C usage graph to interpret independently (#2089).
3507    /// Macro evidence comes from this visibility index at the exact byte; no
3508    /// source-text parsing or terminal-name fallback is used.
3509    pub fn recovered_c_reference_ranges(
3510        &self,
3511        file: &ProjectFile,
3512        root: Node<'_>,
3513        source: &str,
3514        limit: usize,
3515    ) -> RecoveredCReferenceRanges {
3516        if !is_c_source_file(file) {
3517            return RecoveredCReferenceRanges::Complete(Vec::new());
3518        }
3519        let mut ranges = Vec::new();
3520        let mut seen = HashSet::default();
3521        let mut stack = vec![(root, root.is_error())];
3522        while let Some((node, inside_error)) = stack.pop() {
3523            let inside_error = inside_error || node.is_error();
3524            if node.kind() == "preproc_arg" {
3525                // Tree-sitter keeps an object-like replacement opaque. The
3526                // inverse extractor uses the same parsed replacement helper;
3527                // retain its exact macro/type leaves for precision membership
3528                // even when the preprocessor node is outside ERROR recovery.
3529                let macro_value_kind = node.parent().and_then(|parent| {
3530                    (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3531                });
3532                if matches!(
3533                    macro_value_kind,
3534                    Some("preproc_def" | "preproc_function_def")
3535                ) {
3536                    let name = node_text(node, source);
3537                    if !name.is_empty()
3538                        && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3539                        && !push_recovered_c_range(
3540                            &mut ranges,
3541                            &mut seen,
3542                            node.start_byte(),
3543                            node.end_byte(),
3544                            node,
3545                            limit,
3546                        )
3547                    {
3548                        return RecoveredCReferenceRanges::LimitExceeded;
3549                    }
3550                }
3551                if macro_value_kind == Some("preproc_def") {
3552                    for reference in object_macro_replacement_type_references(node, source) {
3553                        for range in reference.component_ranges {
3554                            let visible = self
3555                                .visible_identifier_candidates(file, &source[range.clone()])
3556                                .any(|candidate| {
3557                                    candidate.is_class()
3558                                        || candidate.is_module()
3559                                        || is_type_alias(candidate)
3560                                });
3561                            if visible
3562                                && !push_recovered_c_range(
3563                                    &mut ranges,
3564                                    &mut seen,
3565                                    range.start,
3566                                    range.end,
3567                                    node,
3568                                    limit,
3569                                )
3570                            {
3571                                return RecoveredCReferenceRanges::LimitExceeded;
3572                            }
3573                        }
3574                    }
3575                }
3576            }
3577            if inside_error
3578                && recovered_c_reference_node(self, file, node, source)
3579                && !push_recovered_c_range(
3580                    &mut ranges,
3581                    &mut seen,
3582                    node.start_byte(),
3583                    node.end_byte(),
3584                    node,
3585                    limit,
3586                )
3587            {
3588                return RecoveredCReferenceRanges::LimitExceeded;
3589            }
3590            let mut cursor = node.walk();
3591            for child in node.named_children(&mut cursor) {
3592                stack.push((child, inside_error));
3593            }
3594        }
3595        ranges.sort_unstable();
3596        RecoveredCReferenceRanges::Complete(ranges)
3597    }
3598
3599    /// Whether this target is an indexed macro visible from this file.
3600    ///
3601    /// An unresolved conditional can make more than one same-name macro a
3602    /// possible active binding. Each possible target can keep the site as an
3603    /// unproven hit. A macro in an unrelated translation unit stays excluded.
3604    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3605        self.visible_identifier_candidates(file, target.identifier())
3606            .filter(|candidate| candidate.is_macro())
3607            .any(|candidate| {
3608                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3609            })
3610    }
3611
3612    pub fn object_macro_replacement_at(
3613        &self,
3614        file: &ProjectFile,
3615        name: &str,
3616        before_byte: usize,
3617    ) -> Option<String> {
3618        let environment = self.macro_environment(file, before_byte);
3619        let binding = environment.binding(name)?;
3620        if !binding.exact {
3621            return None;
3622        }
3623        match &binding.definition {
3624            MacroDefinition::Object { replacement } => Some(replacement.clone()),
3625            MacroDefinition::Function { .. }
3626            | MacroDefinition::VariadicFunction { .. }
3627            | MacroDefinition::Unsupported => None,
3628        }
3629    }
3630
3631    fn apply_macro_events(
3632        &self,
3633        file: &ProjectFile,
3634        before_byte: Option<usize>,
3635        environment: &mut MacroEnvironment,
3636        include_stack: &mut HashSet<ProjectFile>,
3637    ) {
3638        if !include_stack.insert(file.clone()) {
3639            return;
3640        }
3641        if self.cpp.prepared_syntax(self.token, file).is_none() {
3642            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3643            include_stack.remove(file);
3644            return;
3645        }
3646        match self.macro_include_protection(file) {
3647            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3648                Some(binding) if binding.is_exact() => {
3649                    include_stack.remove(file);
3650                    return;
3651                }
3652                Some(_) | None if environment.unknown_names => {
3653                    let mut ambiguous_seen = HashSet::default();
3654                    self.mark_macro_events_ambiguous(
3655                        file,
3656                        environment,
3657                        &mut ambiguous_seen,
3658                        file,
3659                        before_byte.unwrap_or_default(),
3660                    );
3661                    include_stack.remove(file);
3662                    return;
3663                }
3664                Some(_) => {
3665                    let mut ambiguous_seen = HashSet::default();
3666                    self.mark_macro_events_ambiguous(
3667                        file,
3668                        environment,
3669                        &mut ambiguous_seen,
3670                        file,
3671                        before_byte.unwrap_or_default(),
3672                    );
3673                    include_stack.remove(file);
3674                    return;
3675                }
3676                None => {}
3677            },
3678            MacroIncludeProtection::PragmaOnce => {
3679                if !environment.applied_pragma_once_files.insert(file.clone()) {
3680                    include_stack.remove(file);
3681                    return;
3682                }
3683                if environment.maybe_applied_pragma_once_files.remove(file) {
3684                    // A prior conditional include may already have consumed the pragma-once
3685                    // header. This unconditional include guarantees it is consumed now, but
3686                    // cannot prove whether its events occur before or after intervening local
3687                    // macro changes, so preserve the union as ambiguous.
3688                    let mut ambiguous_seen = HashSet::default();
3689                    environment.applied_pragma_once_files.remove(file);
3690                    self.mark_macro_events_ambiguous(
3691                        file,
3692                        environment,
3693                        &mut ambiguous_seen,
3694                        file,
3695                        before_byte.unwrap_or_default(),
3696                    );
3697                    environment.maybe_applied_pragma_once_files.remove(file);
3698                    environment.applied_pragma_once_files.insert(file.clone());
3699                    include_stack.remove(file);
3700                    return;
3701                }
3702            }
3703            MacroIncludeProtection::None => {}
3704        }
3705        let cell = self.macro_event_cell(file);
3706        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3707        for event in events {
3708            if before_byte.is_some_and(|limit| event.byte() >= limit) {
3709                break;
3710            }
3711            self.apply_macro_event(file, event, environment, include_stack);
3712        }
3713        include_stack.remove(file);
3714    }
3715
3716    fn apply_macro_event(
3717        &self,
3718        file: &ProjectFile,
3719        event: &MacroEvent,
3720        environment: &mut MacroEnvironment,
3721        include_stack: &mut HashSet<ProjectFile>,
3722    ) {
3723        #[cfg(any(test, feature = "test-support"))]
3724        self.macro_event_application_count
3725            .fetch_add(1, Ordering::Relaxed);
3726        match event {
3727            MacroEvent::Define {
3728                name,
3729                binding,
3730                conditionals,
3731                byte,
3732            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3733                Some(true) => environment.insert(name.clone(), binding.clone()),
3734                Some(false) => {}
3735                None => Self::merge_conditional_macro_definition(
3736                    environment,
3737                    name,
3738                    binding,
3739                    file,
3740                    *byte,
3741                ),
3742            },
3743            MacroEvent::Undef {
3744                name,
3745                conditionals,
3746                byte,
3747            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3748                Some(true) => environment.remove(name),
3749                Some(false) => {}
3750                None => {
3751                    if environment.binding(name).is_some() {
3752                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3753                    }
3754                }
3755            },
3756            MacroEvent::Include {
3757                targets,
3758                conditionals,
3759                byte,
3760            } => {
3761                let condition =
3762                    self.macro_event_condition_value(file, *byte, environment, conditionals);
3763                if condition == Some(false) {
3764                    return;
3765                }
3766                if targets.is_empty() {
3767                    environment.mark_unknown_names(file, *byte);
3768                    return;
3769                }
3770                if condition.is_none() || targets.len() > 1 {
3771                    let mut ambiguous_seen = HashSet::default();
3772                    for target in targets {
3773                        self.mark_macro_events_ambiguous(
3774                            target,
3775                            environment,
3776                            &mut ambiguous_seen,
3777                            file,
3778                            *byte,
3779                        );
3780                    }
3781                } else if let Some(target) = targets.first() {
3782                    self.apply_macro_events(target, None, environment, include_stack);
3783                }
3784            }
3785            MacroEvent::Invalidate { byte } => {
3786                for binding in environment.bindings.values_mut() {
3787                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
3788                }
3789            }
3790        }
3791    }
3792
3793    /// Evaluate the structured conditional path that owns one macro event.
3794    ///
3795    /// `Some(true)` and `Some(false)` are proofs from exact macro bindings at
3796    /// this source byte. `None` preserves the old conditional merge when a
3797    /// build/configuration input or an unsupported expression is involved.
3798    ///
3799    /// `conditionals` is the event's own [`OwningPreprocessorConditionals`]:
3800    /// which ancestors structurally own the event was decided when the events
3801    /// were collected, so all this walk does is find those nodes again and ask
3802    /// the environment what their conditions are worth here.
3803    fn macro_event_condition_value(
3804        &self,
3805        file: &ProjectFile,
3806        event_byte: usize,
3807        environment: &MacroEnvironment,
3808        conditionals: &OwningPreprocessorConditionals,
3809    ) -> Option<bool> {
3810        if conditionals.is_empty() {
3811            return Some(true);
3812        }
3813        let prepared = self.cpp.prepared_syntax(self.token, file)?;
3814        let source = prepared.source();
3815        let root = prepared.tree().root_node();
3816        let descendant = root.descendant_for_byte_range(
3817            event_byte,
3818            event_byte.saturating_add(1).min(source.len()),
3819        )?;
3820        let mut unknown = false;
3821        let mut current = descendant.parent();
3822        while let Some(conditional) = current {
3823            if matches!(
3824                conditional.kind(),
3825                "preproc_if" | "preproc_ifdef" | "preproc_elif"
3826            ) && conditionals.contains(&conditional.start_byte())
3827            {
3828                let mut value = match conditional.kind() {
3829                    "preproc_ifdef" => {
3830                        let name = conditional.child_by_field_name("name")?;
3831                        let defined =
3832                            self.macro_name_defined_value(environment, node_text(name, source));
3833                        match conditional.child(0)?.kind() {
3834                            "#ifdef" => defined,
3835                            "#ifndef" => defined.map(|defined| !defined),
3836                            _ => None,
3837                        }
3838                    }
3839                    "preproc_if" | "preproc_elif" => conditional
3840                        .child_by_field_name("condition")
3841                        .and_then(|condition| {
3842                            self.preprocessor_integer_value(
3843                                condition,
3844                                source,
3845                                environment,
3846                                &mut Vec::new(),
3847                                0,
3848                            )
3849                        })
3850                        .map(|value| value != 0),
3851                    _ => unreachable!(),
3852                };
3853                if conditional
3854                    .child_by_field_name("alternative")
3855                    .is_some_and(|alternative| {
3856                        alternative.start_byte() <= descendant.start_byte()
3857                            && descendant.end_byte() <= alternative.end_byte()
3858                    })
3859                {
3860                    value = value.map(|value| !value);
3861                }
3862                match value {
3863                    Some(true) => {}
3864                    Some(false) => return Some(false),
3865                    None => unknown = true,
3866                }
3867            }
3868            current = conditional.parent();
3869        }
3870        (!unknown).then_some(true)
3871    }
3872
3873    fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
3874        if environment.known_undefined_names.contains(name) {
3875            return Some(false);
3876        }
3877        if let Some(binding) = environment.binding(name) {
3878            return binding.is_exact().then_some(true);
3879        }
3880        environment
3881            .build_proven_defines
3882            .contains(name)
3883            .then_some(true)
3884    }
3885
3886    fn preprocessor_integer_value(
3887        &self,
3888        expression: Node<'_>,
3889        source: &str,
3890        environment: &MacroEnvironment,
3891        expansion_stack: &mut Vec<(ProjectFile, usize)>,
3892        depth: usize,
3893    ) -> Option<i128> {
3894        // Macro replacement graphs can cycle. This explicit bound makes the
3895        // otherwise recursive AST evaluation stack-safe for hostile input.
3896        if depth >= 64 {
3897            return None;
3898        }
3899        match expression.kind() {
3900            "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
3901            "identifier" | "type_identifier" => {
3902                let binding = environment.binding(node_text(expression, source))?;
3903                if !binding.is_exact() {
3904                    return None;
3905                }
3906                let MacroDefinition::Object { replacement } = &binding.definition else {
3907                    return None;
3908                };
3909                let identity = (binding.source.clone(), binding.declaration_byte);
3910                if expansion_stack.contains(&identity) {
3911                    return None;
3912                }
3913                expansion_stack.push(identity);
3914                let parsed = self.parsed_macro_replacement(binding, replacement);
3915                let value = match parsed.as_ref() {
3916                    ParsedMacroReplacement::Parsed {
3917                        source: replacement_source,
3918                        tree,
3919                    } => first_descendant_of_kind(tree.root_node(), "call_expression")
3920                        .and_then(|call| call.child_by_field_name("arguments"))
3921                        .and_then(|arguments| argument_children(arguments).next())
3922                        .and_then(|argument| {
3923                            self.preprocessor_integer_value(
3924                                argument,
3925                                replacement_source,
3926                                environment,
3927                                expansion_stack,
3928                                depth + 1,
3929                            )
3930                        }),
3931                    ParsedMacroReplacement::Unsupported => None,
3932                };
3933                expansion_stack.pop();
3934                value
3935            }
3936            "preproc_defined" => {
3937                let mut cursor = expression.walk();
3938                let name = expression
3939                    .named_children(&mut cursor)
3940                    .find(|child| child.kind() == "identifier")?;
3941                self.macro_name_defined_value(environment, node_text(name, source))
3942                    .map(i128::from)
3943            }
3944            "parenthesized_expression" => expression.named_child(0).and_then(|child| {
3945                self.preprocessor_integer_value(
3946                    child,
3947                    source,
3948                    environment,
3949                    expansion_stack,
3950                    depth + 1,
3951                )
3952            }),
3953            "unary_expression" => {
3954                let operator = expression.child_by_field_name("operator")?.kind();
3955                let argument = expression.child_by_field_name("argument")?;
3956                let value = self.preprocessor_integer_value(
3957                    argument,
3958                    source,
3959                    environment,
3960                    expansion_stack,
3961                    depth + 1,
3962                )?;
3963                match operator {
3964                    "+" => Some(value),
3965                    "-" => value.checked_neg(),
3966                    "!" => Some(i128::from(value == 0)),
3967                    "~" => Some(!value),
3968                    _ => None,
3969                }
3970            }
3971            "binary_expression" => {
3972                let left = self.preprocessor_integer_value(
3973                    expression.child_by_field_name("left")?,
3974                    source,
3975                    environment,
3976                    expansion_stack,
3977                    depth + 1,
3978                )?;
3979                let right = self.preprocessor_integer_value(
3980                    expression.child_by_field_name("right")?,
3981                    source,
3982                    environment,
3983                    expansion_stack,
3984                    depth + 1,
3985                )?;
3986                match expression.child_by_field_name("operator")?.kind() {
3987                    "+" => left.checked_add(right),
3988                    "-" => left.checked_sub(right),
3989                    "*" => left.checked_mul(right),
3990                    "/" => left.checked_div(right),
3991                    "%" => left.checked_rem(right),
3992                    "<<" => u32::try_from(right)
3993                        .ok()
3994                        .and_then(|shift| left.checked_shl(shift)),
3995                    ">>" => u32::try_from(right)
3996                        .ok()
3997                        .and_then(|shift| left.checked_shr(shift)),
3998                    "<" => Some(i128::from(left < right)),
3999                    "<=" => Some(i128::from(left <= right)),
4000                    ">" => Some(i128::from(left > right)),
4001                    ">=" => Some(i128::from(left >= right)),
4002                    "==" => Some(i128::from(left == right)),
4003                    "!=" => Some(i128::from(left != right)),
4004                    "&" => Some(left & right),
4005                    "|" => Some(left | right),
4006                    "^" => Some(left ^ right),
4007                    "&&" => Some(i128::from(left != 0 && right != 0)),
4008                    "||" => Some(i128::from(left != 0 || right != 0)),
4009                    _ => None,
4010                }
4011            }
4012            _ => None,
4013        }
4014    }
4015
4016    fn mark_macro_events_ambiguous(
4017        &self,
4018        file: &ProjectFile,
4019        environment: &mut MacroEnvironment,
4020        include_stack: &mut HashSet<ProjectFile>,
4021        conditional_file: &ProjectFile,
4022        conditional_byte: usize,
4023    ) {
4024        if !include_stack.insert(file.clone()) {
4025            return;
4026        }
4027        if self.cpp.prepared_syntax(self.token, file).is_none() {
4028            environment.mark_unknown_names(conditional_file, conditional_byte);
4029            return;
4030        }
4031        match self.macro_include_protection(file) {
4032            MacroIncludeProtection::MacroGuard(guard) => {
4033                if environment
4034                    .binding(&guard)
4035                    .is_some_and(MacroBinding::is_exact)
4036                {
4037                    return;
4038                }
4039            }
4040            MacroIncludeProtection::PragmaOnce => {
4041                if environment.applied_pragma_once_files.contains(file) {
4042                    return;
4043                }
4044                environment
4045                    .maybe_applied_pragma_once_files
4046                    .insert(file.clone());
4047            }
4048            MacroIncludeProtection::None => {}
4049        }
4050        let cell = self.macro_event_cell(file);
4051        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4052        for event in events {
4053            #[cfg(any(test, feature = "test-support"))]
4054            self.macro_event_application_count
4055                .fetch_add(1, Ordering::Relaxed);
4056            match event {
4057                MacroEvent::Define { name, binding, .. } => {
4058                    Self::merge_conditional_macro_definition(
4059                        environment,
4060                        name,
4061                        binding,
4062                        conditional_file,
4063                        conditional_byte,
4064                    );
4065                }
4066                MacroEvent::Undef { name, .. } => {
4067                    if environment.binding(name).is_some() {
4068                        environment.insert(
4069                            name.clone(),
4070                            MacroBinding::ambiguous(conditional_file, conditional_byte),
4071                        );
4072                    } else {
4073                        environment.remove_known_undefined(name);
4074                    }
4075                }
4076                MacroEvent::Include { targets, .. } => {
4077                    if targets.is_empty() {
4078                        environment.mark_unknown_names(conditional_file, conditional_byte);
4079                        continue;
4080                    }
4081                    for target in targets {
4082                        self.mark_macro_events_ambiguous(
4083                            target,
4084                            environment,
4085                            include_stack,
4086                            conditional_file,
4087                            conditional_byte,
4088                        );
4089                    }
4090                }
4091                MacroEvent::Invalidate { .. } => {
4092                    for binding in environment.bindings.values_mut() {
4093                        *binding = MacroBinding::uncertain_from(
4094                            binding,
4095                            conditional_file,
4096                            conditional_byte,
4097                        );
4098                    }
4099                }
4100            }
4101        }
4102    }
4103
4104    fn merge_conditional_macro_definition(
4105        environment: &mut MacroEnvironment,
4106        name: &str,
4107        possible_binding: &MacroBinding,
4108        conditional_file: &ProjectFile,
4109        conditional_byte: usize,
4110    ) {
4111        // A conditional include can revisit an already-active guarded header.
4112        // If the possible branch defines the exact same macro, both outcomes
4113        // leave the binding unchanged; degrading it to Unknown would discard
4114        // proof because of an unrelated unresolved macro name (#2092).
4115        if environment.binding(name).is_some_and(|current| {
4116            current.definition != MacroDefinition::Unsupported
4117                && current.definition == possible_binding.definition
4118        }) {
4119            return;
4120        }
4121        environment.insert(
4122            name.to_string(),
4123            MacroBinding::ambiguous(conditional_file, conditional_byte),
4124        );
4125    }
4126
4127    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4128        let cell = self
4129            .macro_include_protection_cells
4130            .lock()
4131            .expect("C++ include protection cache poisoned")
4132            .entry(file.clone())
4133            .or_default()
4134            .clone();
4135        cell.get_or_init(|| {
4136            self.cpp.prepared_syntax(self.token, file).map_or(
4137                MacroIncludeProtection::None,
4138                |prepared| {
4139                    top_level_macro_include_protection(
4140                        prepared.tree().root_node(),
4141                        prepared.source(),
4142                    )
4143                },
4144            )
4145        })
4146        .clone()
4147    }
4148
4149    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4150        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4151            return Vec::new();
4152        };
4153        let source = prepared.source();
4154        let mut events = Vec::new();
4155        let root = prepared.tree().root_node();
4156        let mut stack = vec![root];
4157        while let Some(node) = stack.pop() {
4158            match node.kind() {
4159                "preproc_def" | "preproc_function_def" => {
4160                    let Some(name) = node.child_by_field_name("name") else {
4161                        continue;
4162                    };
4163                    let name = node_text(name, source).to_string();
4164                    events.push(MacroEvent::Define {
4165                        name,
4166                        binding: MacroBinding {
4167                            source: file.clone(),
4168                            declaration_byte: node.start_byte(),
4169                            definition: Self::decode_macro_definition(node, source),
4170                            exact: true,
4171                        },
4172                        byte: node.start_byte(),
4173                        conditionals: owning_preprocessor_conditionals(root, node, source),
4174                    });
4175                    continue;
4176                }
4177                "preproc_include" => {
4178                    let Some(path) = node.child_by_field_name("path") else {
4179                        events.push(MacroEvent::Include {
4180                            targets: Vec::new(),
4181                            byte: node.start_byte(),
4182                            conditionals: owning_preprocessor_conditionals(root, node, source),
4183                        });
4184                        continue;
4185                    };
4186                    let include = structured_include_path(path, source);
4187                    let targets = include.map_or_else(Vec::new, |include| {
4188                        resolve_include_targets_with_index(
4189                            file,
4190                            include,
4191                            self.cpp.include_target_index(),
4192                        )
4193                    });
4194                    // An include no indexed file can answer -- its final path component names
4195                    // no analyzable workspace file -- crosses into sources this index does not
4196                    // hold, so it cannot hide indexed macro state and must not poison every
4197                    // later local macro binding. The include's spelling does not decide that:
4198                    // a quoted include falls back to the same search path an angle include
4199                    // uses, and esphome's `core/log.h` reaches every component with
4200                    // `#include "WString.h"`, an Arduino header absent from the tree (#3057).
4201                    // A computed include, and a spelling some indexed file name does match
4202                    // without resolving to one target, may still hide indexed macro state and
4203                    // therefore fail closed.
4204                    if targets.is_empty()
4205                        && include.is_some_and(|include| {
4206                            !self.cpp.include_target_index().names_indexed_file(include)
4207                        })
4208                    {
4209                        continue;
4210                    }
4211                    events.push(MacroEvent::Include {
4212                        targets,
4213                        byte: node.start_byte(),
4214                        conditionals: owning_preprocessor_conditionals(root, node, source),
4215                    });
4216                    continue;
4217                }
4218                "preproc_call" => {
4219                    let Some(directive) = node.child_by_field_name("directive") else {
4220                        continue;
4221                    };
4222                    if node_text(directive, source) != "#undef" {
4223                        continue;
4224                    }
4225                    let name = node
4226                        .child_by_field_name("argument")
4227                        .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
4228                    if let Some(name) = name {
4229                        events.push(MacroEvent::Undef {
4230                            name,
4231                            byte: node.start_byte(),
4232                            conditionals: owning_preprocessor_conditionals(root, node, source),
4233                        });
4234                    } else {
4235                        events.push(MacroEvent::Invalidate {
4236                            byte: node.start_byte(),
4237                        });
4238                    }
4239                    continue;
4240                }
4241                _ => {}
4242            }
4243            push_named_children_reversed(node, &mut stack);
4244        }
4245        events.sort_by_key(MacroEvent::byte);
4246        events
4247    }
4248
4249    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4250        self.ordinary_type_import_cells
4251            .lock()
4252            .expect("C++ ordinary type import cache poisoned")
4253            .entry(file.clone())
4254            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4255            .clone()
4256    }
4257
4258    pub fn project_using_index(
4259        &self,
4260        build: impl FnOnce() -> ProjectUsingIndex,
4261    ) -> &ProjectUsingIndex {
4262        self.project_using_index.get_or_init(build)
4263    }
4264
4265    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4266        let mut files = self
4267            .visible_source_files_by_root
4268            .values()
4269            .flatten()
4270            .cloned()
4271            .collect::<HashSet<_>>()
4272            .into_iter()
4273            .collect::<Vec<_>>();
4274        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4275        files
4276    }
4277
4278    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4279        self.visible_source_files_by_root
4280            .get(root)
4281            .is_some_and(|files| files.contains(source))
4282    }
4283
4284    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4285        let cached = self
4286            .visible_parser_alias_name_sets
4287            .read()
4288            .expect("visible parser alias-name cache poisoned")
4289            .get(file)
4290            .cloned();
4291        let cell = if let Some(cached) = cached {
4292            cached
4293        } else {
4294            let mut cells = self
4295                .visible_parser_alias_name_sets
4296                .write()
4297                .expect("visible parser alias-name cache poisoned");
4298            Arc::clone(
4299                cells
4300                    .entry(file.clone())
4301                    .or_insert_with(|| Arc::new(OnceLock::new())),
4302            )
4303        };
4304        cell.get_or_init(|| {
4305            #[cfg(any(test, feature = "test-support"))]
4306            self.visible_parser_alias_name_set_build_count
4307                .fetch_add(1, Ordering::Relaxed);
4308            let mut names = HashSet::default();
4309            let visible_files = self
4310                .visible_source_files_by_root
4311                .get(file)
4312                .cloned()
4313                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4314            for visible_file in visible_files {
4315                let aliases = {
4316                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4317                    Arc::clone(
4318                        cells
4319                            .entry(visible_file.clone())
4320                            .or_insert_with(|| Arc::new(OnceLock::new())),
4321                    )
4322                };
4323                for alias in aliases
4324                    .get_or_init(|| {
4325                        self.parser_alias_source_parses
4326                            .fetch_add(1, Ordering::Relaxed);
4327                        #[cfg(any(test, feature = "test-support"))]
4328                        {
4329                            *self
4330                                .alias_source_parse_counts
4331                                .lock()
4332                                .expect("alias source parse count lock")
4333                                .entry(visible_file.clone())
4334                                .or_default() += 1;
4335                        }
4336                        aliases_from_prepared_source(self.cpp, self.token, &visible_file)
4337                            .into_boxed_slice()
4338                    })
4339                    .iter()
4340                {
4341                    names.insert(alias.name.clone());
4342                }
4343            }
4344            names
4345        })
4346        .contains(name)
4347    }
4348
4349    pub fn parser_alias_name_may_resolve_to_target(
4350        &self,
4351        file: &ProjectFile,
4352        alias_name: &str,
4353        target: &CodeUnit,
4354    ) -> bool {
4355        let started = std::time::Instant::now();
4356        self.parser_alias_fallback_calls
4357            .fetch_add(1, Ordering::Relaxed);
4358        let key = (
4359            file.clone(),
4360            alias_name.to_string(),
4361            logical_symbol_key(target),
4362        );
4363        let cached = self
4364            .parser_alias_target_matches
4365            .read()
4366            .expect("parser alias target-match cache poisoned")
4367            .get(&key)
4368            .cloned();
4369        let cell = if let Some(cached) = cached {
4370            cached
4371        } else {
4372            let mut cells = self
4373                .parser_alias_target_matches
4374                .write()
4375                .expect("parser alias target-match cache poisoned");
4376            Arc::clone(
4377                cells
4378                    .entry(key)
4379                    .or_insert_with(|| Arc::new(OnceLock::new())),
4380            )
4381        };
4382        let matched = *cell.get_or_init(|| match self.visible_source_files_by_root.get(file) {
4383            None => {
4384                self.parser_alias_fallback_files
4385                    .fetch_add(1, Ordering::Relaxed);
4386                self.file_alias_matches(self.cpp, file, alias_name, target)
4387            }
4388            Some(visible_files) => visible_files.iter().any(|visible_file| {
4389                self.parser_alias_fallback_files
4390                    .fetch_add(1, Ordering::Relaxed);
4391                self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4392            }),
4393        });
4394        self.parser_alias_fallback_elapsed_micros.fetch_add(
4395            started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4396            Ordering::Relaxed,
4397        );
4398        matched
4399    }
4400
4401    fn file_alias_matches(
4402        &self,
4403        cpp: &dyn CppSource,
4404        file: &ProjectFile,
4405        alias_name: &str,
4406        target: &CodeUnit,
4407    ) -> bool {
4408        let cell = {
4409            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4410            Arc::clone(
4411                cells
4412                    .entry(file.clone())
4413                    .or_insert_with(|| Arc::new(OnceLock::new())),
4414            )
4415        };
4416        cell.get_or_init(|| {
4417            self.parser_alias_source_parses
4418                .fetch_add(1, Ordering::Relaxed);
4419            #[cfg(any(test, feature = "test-support"))]
4420            {
4421                *self
4422                    .alias_source_parse_counts
4423                    .lock()
4424                    .expect("alias source parse count lock")
4425                    .entry(file.clone())
4426                    .or_default() += 1;
4427            }
4428            aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
4429        })
4430        .iter()
4431        .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
4432    }
4433
4434    fn callable_arities_for_target(
4435        &self,
4436        analyzer: &CppGraphSource<'_>,
4437        cpp: &dyn CppSource,
4438        file: &ProjectFile,
4439        prepared: &PreparedSyntaxTree,
4440        spec: &TargetSpec,
4441    ) -> Vec<ActivatedCallableArity> {
4442        let Some(signature) = spec.target.signature() else {
4443            return Vec::new();
4444        };
4445        let Some(candidates) = self
4446            .visible_by_identifier
4447            .get(file)
4448            .and_then(|by_name| by_name.get(&spec.member_name))
4449        else {
4450            return Vec::new();
4451        };
4452        let differing_candidates = candidates
4453            .iter()
4454            .filter(|candidate| {
4455                candidate.is_function()
4456                    && candidate.fq_name() == spec.target.fq_name()
4457                    && candidate.signature() == Some(signature)
4458            })
4459            .filter_map(|candidate| {
4460                analyzer
4461                    .signature_metadata(candidate)
4462                    .into_iter()
4463                    .find_map(|metadata| metadata.callable_arity())
4464                    .filter(|arity| Some(*arity) != spec.callable_arity)
4465                    .map(|arity| (candidate, arity))
4466            })
4467            .collect::<Vec<_>>();
4468        if differing_candidates.is_empty() {
4469            return Vec::new();
4470        }
4471        let mut arities = Vec::with_capacity(differing_candidates.len());
4472        // The activation ranges here describe the whole file rather than one
4473        // reference, so there is no reference guard environment to consult.
4474        let reference = CallableReferenceContext {
4475            file,
4476            position: None,
4477        };
4478        for (candidate, candidate_arity) in differing_candidates {
4479            let declaration_activation = if candidate.source() == file {
4480                callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
4481            } else {
4482                cpp.prepared_syntax(self.token, candidate.source())
4483                    .and_then(|syntax| {
4484                        callable_declaration_activation_in_file(
4485                            analyzer,
4486                            syntax.as_ref(),
4487                            candidate,
4488                            &reference,
4489                        )
4490                    })
4491            };
4492            let Some(declaration_activation) = declaration_activation else {
4493                continue;
4494            };
4495            let activation_byte = if candidate.source() == file {
4496                Some(declaration_activation)
4497            } else {
4498                self.include_activation_for_source(cpp, file, prepared, candidate.source())
4499            };
4500            if let Some(activation_byte) = activation_byte {
4501                arities.push(ActivatedCallableArity {
4502                    activation_byte,
4503                    arity: candidate_arity,
4504                });
4505            }
4506        }
4507        arities
4508    }
4509
4510    fn callable_parameter_macro_arity(
4511        &self,
4512        target: &CodeUnit,
4513        signature: Option<&str>,
4514    ) -> Option<CallableArity> {
4515        let parameter_types = cpp_signature_param_types(signature?)?;
4516        let [macro_name] = parameter_types.as_slice() else {
4517            return None;
4518        };
4519        if macro_name.is_empty()
4520            || !macro_name
4521                .chars()
4522                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4523        {
4524            return None;
4525        }
4526        let cache_key = (target.source().clone(), macro_name.clone());
4527        if let Some(cached) = self
4528            .callable_parameter_macro_arities
4529            .lock()
4530            .expect("C++ callable parameter-macro arity cache poisoned")
4531            .get(&cache_key)
4532            .copied()
4533        {
4534            return cached;
4535        }
4536        let mut visible_files = HashSet::default();
4537        collect_include_closure(
4538            &self.cpp_source(),
4539            self.cpp.include_target_index(),
4540            target.source(),
4541            &mut visible_files,
4542            None,
4543        );
4544        let mut arities = Vec::new();
4545        for visible_file in visible_files {
4546            let cell = self.macro_event_cell(&visible_file);
4547            for event in
4548                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4549            {
4550                let MacroEvent::Define { name, binding, .. } = event else {
4551                    continue;
4552                };
4553                if name != macro_name {
4554                    continue;
4555                }
4556                let MacroDefinition::Object { replacement } = &binding.definition else {
4557                    continue;
4558                };
4559                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4560                    continue;
4561                };
4562                if !arities.contains(&arity) {
4563                    arities.push(arity);
4564                }
4565            }
4566        }
4567        let resolved = (|| {
4568            let required = arities
4569                .iter()
4570                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4571                .min()?;
4572            let total = arities.iter().map(|arity| arity.total()).max()?;
4573            let repeated = arities
4574                .iter()
4575                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4576            // Preprocessor conditions can leave more than one object-like parameter
4577            // bundle active in the target header's include closure. Preserve their
4578            // conservative callable envelope instead of choosing whichever definition
4579            // happened to be visited first.
4580            Some(CallableArity::new(required, total, repeated))
4581        })();
4582        self.callable_parameter_macro_arities
4583            .lock()
4584            .expect("C++ callable parameter-macro arity cache poisoned")
4585            .insert(cache_key, resolved);
4586        resolved
4587    }
4588
4589    pub fn include_activation_for_source(
4590        &self,
4591        cpp: &dyn CppSource,
4592        file: &ProjectFile,
4593        prepared: &PreparedSyntaxTree,
4594        donor_source: &ProjectFile,
4595    ) -> Option<usize> {
4596        let key = (file.clone(), donor_source.clone());
4597        if let Some(cached) = self
4598            .include_activation_cells
4599            .lock()
4600            .expect("C++ include activation cache poisoned")
4601            .get(&key)
4602            .copied()
4603        {
4604            return cached;
4605        }
4606        #[cfg(any(test, feature = "test-support"))]
4607        self.include_activation_build_count
4608            .fetch_add(1, Ordering::Relaxed);
4609        let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
4610        let mut cells = self
4611            .include_activation_cells
4612            .lock()
4613            .expect("C++ include activation cache poisoned");
4614        *cells.entry(key).or_insert(activation)
4615    }
4616
4617    pub fn conditional_include_projections_for_source(
4618        &self,
4619        file: &ProjectFile,
4620        prepared: &PreparedSyntaxTree,
4621        donor_source: &ProjectFile,
4622    ) -> Arc<[ConditionalIncludeProjection]> {
4623        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4624        let cell = self
4625            .conditional_include_projection_cells
4626            .lock()
4627            .expect("C++ conditional include projection cache poisoned")
4628            .entry(file.clone())
4629            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4630            .clone();
4631        let index = cell.get_or_build_pool_independent(|| {
4632            #[cfg(any(test, feature = "test-support"))]
4633            self.conditional_include_projection_index_build_count
4634                .fetch_add(1, Ordering::Relaxed);
4635            find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
4636                #[cfg(any(test, feature = "test-support"))]
4637                self.conditional_include_projection_state_count
4638                    .fetch_add(1, Ordering::Relaxed);
4639            })
4640        });
4641        index
4642            .get(donor_source)
4643            .cloned()
4644            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4645    }
4646
4647    #[cfg(any(test, feature = "test-support"))]
4648    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4649        (
4650            self.conditional_include_projection_index_build_count
4651                .load(Ordering::Relaxed),
4652            self.conditional_include_projection_state_count
4653                .load(Ordering::Relaxed),
4654        )
4655    }
4656
4657    #[cfg(any(test, feature = "test-support"))]
4658    pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4659        self.conditional_include_target_state_count
4660            .load(Ordering::Relaxed)
4661    }
4662
4663    #[cfg(any(test, feature = "test-support"))]
4664    pub fn include_activation_build_count_for_test(&self) -> usize {
4665        self.include_activation_build_count.load(Ordering::Relaxed)
4666    }
4667
4668    #[cfg(any(test, feature = "test-support"))]
4669    pub fn note_using_donor_activation_for_test(&self) {
4670        self.using_donor_activation_count
4671            .fetch_add(1, Ordering::Relaxed);
4672    }
4673
4674    #[cfg(not(any(test, feature = "test-support")))]
4675    pub fn note_using_donor_activation_for_test(&self) {}
4676
4677    #[cfg(any(test, feature = "test-support"))]
4678    pub fn note_using_namespace_lookup_for_test(&self) {
4679        self.using_namespace_lookup_count
4680            .fetch_add(1, Ordering::Relaxed);
4681    }
4682
4683    #[cfg(not(any(test, feature = "test-support")))]
4684    pub fn note_using_namespace_lookup_for_test(&self) {}
4685
4686    #[cfg(any(test, feature = "test-support"))]
4687    pub fn note_using_name_candidate_inspection_for_test(&self) {
4688        self.using_name_candidate_inspection_count
4689            .fetch_add(1, Ordering::Relaxed);
4690    }
4691
4692    #[cfg(not(any(test, feature = "test-support")))]
4693    pub fn note_using_name_candidate_inspection_for_test(&self) {}
4694
4695    #[cfg(any(test, feature = "test-support"))]
4696    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4697        (
4698            self.using_donor_activation_count.load(Ordering::Relaxed),
4699            self.using_namespace_lookup_count.load(Ordering::Relaxed),
4700            self.callable_reference_spec_build_count
4701                .load(Ordering::Relaxed),
4702            self.using_name_candidate_inspection_count
4703                .load(Ordering::Relaxed),
4704        )
4705    }
4706
4707    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4708        file == target.source()
4709            || self
4710                .visible_by_file
4711                .get(file)
4712                .is_some_and(|visible| visible.contains(target))
4713    }
4714
4715    /// Whether some declaration of `declaration`'s logical symbol is visible at
4716    /// `reference_byte` in `file`.
4717    ///
4718    /// The question is asked of the *logical* symbol, not of the physical unit:
4719    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
4720    /// and it does not have to be - what makes the call legal is the header
4721    /// declaration that the reference file does include. Reading that relation
4722    /// through `same_logical_callable` rather than through signature strings is
4723    /// the same #2010 correction the gates make, and it matters here because
4724    /// the body and the declaration are exactly the pair that spells one
4725    /// parameter type two ways.
4726    pub fn declaration_visible_at(
4727        &self,
4728        analyzer: &CppGraphSource<'_>,
4729        file: &ProjectFile,
4730        declaration: &CodeUnit,
4731        reference_byte: usize,
4732    ) -> bool {
4733        let reference_guards = OnceCell::new();
4734        self.visible_identifier_candidates(file, declaration.identifier())
4735            .filter(|candidate| {
4736                self.same_logical_callable(analyzer, candidate, declaration)
4737                    || flattened_macro_namespace_declaration_matches(
4738                        analyzer,
4739                        self.cpp,
4740                        file,
4741                        candidate,
4742                        declaration,
4743                        reference_byte,
4744                    )
4745            })
4746            .any(|candidate| {
4747                self.physical_declaration_visible_at(
4748                    analyzer,
4749                    file,
4750                    candidate,
4751                    reference_byte,
4752                    &reference_guards,
4753                )
4754            })
4755    }
4756
4757    /// Whether a physical declaration is visible at an exact structured
4758    /// reference node. Inverse C field references need the reference's own
4759    /// preprocessor environment before using the callable activation path:
4760    /// callable activation can establish that a field declaration is
4761    /// nameable, but it must not admit the opposite branch of that field's
4762    /// conditional family.
4763    pub fn declaration_visible_at_reference(
4764        &self,
4765        analyzer: &CppGraphSource<'_>,
4766        file: &ProjectFile,
4767        declaration: &CodeUnit,
4768        reference: Node<'_>,
4769    ) -> bool {
4770        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4771            return false;
4772        };
4773        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4774        let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4775        if !declaration_guards.iter().any(|(_, required)| {
4776            guards_compatible_at_reference(required, reference_guards.as_ref())
4777        }) {
4778            return false;
4779        }
4780        if declaration.source() == file
4781            && !analyzer.reference_uses_c_semantics(file)
4782            && (declaration.is_field() || declaration.is_callable())
4783            && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4784                self.indexed_enclosing_owner_scope(analyzer, file, reference)
4785                    .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4786            })
4787        {
4788            // C++ complete-class context makes members visible throughout the
4789            // class. The guard check above still excludes a declaration from
4790            // an incompatible preprocessor branch.
4791            return true;
4792        }
4793        if declaration.is_field() && declaration.source() == file {
4794            let reference_byte = reference.start_byte();
4795            let reference_function =
4796                real_function_definition_ancestor(reference, prepared.source());
4797            let guards = OnceCell::new();
4798            let field_reference = CallableReferenceContext {
4799                file,
4800                position: Some(CallableReferencePosition {
4801                    prepared: prepared.as_ref(),
4802                    byte: reference_byte,
4803                    guards: &guards,
4804                }),
4805            };
4806            let mut has_local_declaration = false;
4807            let mut local_declaration_visible = false;
4808            for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4809            {
4810                let Some(declaration_function) =
4811                    real_function_definition_ancestor(declaration, prepared.source())
4812                else {
4813                    continue;
4814                };
4815                has_local_declaration = true;
4816                if reference_function.is_some_and(|reference_function| {
4817                    reference_function.start_byte() == declaration_function.start_byte()
4818                        && reference_function.end_byte() == declaration_function.end_byte()
4819                }) && callable_preprocessor_context_is_visible_for_reference(
4820                    declaration,
4821                    prepared.source(),
4822                    &field_reference,
4823                ) && callable_declaration_activation_byte(declaration) < reference_byte
4824                {
4825                    local_declaration_visible = true;
4826                    break;
4827                }
4828            }
4829            if has_local_declaration {
4830                return local_declaration_visible;
4831            }
4832        }
4833        let guards = OnceCell::new();
4834        self.physical_declaration_visible_at(
4835            analyzer,
4836            file,
4837            declaration,
4838            reference.start_byte(),
4839            &guards,
4840        )
4841    }
4842
4843    /// C forward navigation may bind a call to a later same-file definition.
4844    /// There is no earlier source declaration to activate in that legacy C
4845    /// shape, but the call's preprocessor environment must still imply the
4846    /// definition's requirements. Ordinary C++ and inverse visibility retain
4847    /// the declaration-order rule in [`Self::declaration_visible_at`].
4848    pub fn declaration_visible_for_c_forward_call(
4849        &self,
4850        analyzer: &CppGraphSource<'_>,
4851        file: &ProjectFile,
4852        declaration: &CodeUnit,
4853        reference_byte: usize,
4854    ) -> bool {
4855        if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4856            return true;
4857        }
4858        if declaration.source() != file {
4859            return false;
4860        }
4861        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4862            return false;
4863        };
4864        let reference_guards = prepared
4865            .tree()
4866            .root_node()
4867            .descendant_for_byte_range(reference_byte, reference_byte)
4868            .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4869        declaration_guard_requirements(analyzer, self.cpp, declaration)
4870            .into_iter()
4871            .any(|(_, required)| {
4872                guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4873            })
4874    }
4875
4876    pub fn callable_arity_at_reference(
4877        &self,
4878        analyzer: &CppGraphSource<'_>,
4879        file: &ProjectFile,
4880        candidate: &CodeUnit,
4881        reference_byte: usize,
4882    ) -> Option<CallableArity> {
4883        let key = (file.clone(), logical_symbol_key(candidate));
4884        let cell = self
4885            .callable_reference_specs
4886            .lock()
4887            .expect("C++ callable reference-spec cache poisoned")
4888            .entry(key)
4889            .or_default()
4890            .clone();
4891        let spec = cell.get_or_init(|| {
4892            let prepared = self.cpp.prepared_syntax(self.token, file)?;
4893            let spec = TargetSpec::from_target(analyzer, candidate)?;
4894            let spec = spec
4895                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4896                .into_owned();
4897            #[cfg(any(test, feature = "test-support"))]
4898            self.callable_reference_spec_build_count
4899                .fetch_add(1, Ordering::Relaxed);
4900            Some(spec)
4901        });
4902        spec.as_ref()?.callable_arity_at(reference_byte)
4903    }
4904
4905    fn physical_declaration_visible_at(
4906        &self,
4907        analyzer: &CppGraphSource<'_>,
4908        file: &ProjectFile,
4909        declaration: &CodeUnit,
4910        reference_byte: usize,
4911        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4912    ) -> bool {
4913        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4914            return false;
4915        };
4916        let reference = CallableReferenceContext {
4917            file,
4918            position: Some(CallableReferencePosition {
4919                prepared: prepared.as_ref(),
4920                byte: reference_byte,
4921                guards: reference_guards,
4922            }),
4923        };
4924        if declaration.source() == file {
4925            return callable_declaration_activation_in_file(
4926                analyzer,
4927                prepared.as_ref(),
4928                declaration,
4929                &reference,
4930            )
4931            .or_else(|| {
4932                self.exhaustive_guard_family_activation(
4933                    analyzer,
4934                    prepared.as_ref(),
4935                    declaration,
4936                    &reference,
4937                )
4938            })
4939            .is_some_and(|activation| activation < reference_byte);
4940        }
4941        let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
4942            return false;
4943        };
4944        if self
4945            .foreign_callable_declaration_activation(
4946                analyzer,
4947                donor_syntax.as_ref(),
4948                declaration,
4949                &reference,
4950            )
4951            .or_else(|| {
4952                self.exhaustive_guard_family_activation(
4953                    analyzer,
4954                    donor_syntax.as_ref(),
4955                    declaration,
4956                    &reference,
4957                )
4958            })
4959            .is_none()
4960        {
4961            return false;
4962        }
4963        declaration_guard_requirements(analyzer, self.cpp, declaration)
4964            .into_iter()
4965            .any(|(_, declaration_guards)| {
4966                self.foreign_declaration_reachable_at_reference(
4967                    file,
4968                    prepared.as_ref(),
4969                    declaration.source(),
4970                    &declaration_guards,
4971                    reference.guards(),
4972                    reference_byte,
4973                )
4974            })
4975    }
4976
4977    /// The byte at which `declaration` activates inside the foreign file that
4978    /// `donor_syntax` describes.
4979    ///
4980    /// Which guard rule applies depends on what decides the reference's
4981    /// configuration. When `compile_commands.json` covers the reference's
4982    /// translation unit, the build is the proof source and the declaration's
4983    /// guards must follow from the reference's active guards plus the proven
4984    /// defines; a platform macro the build does not prove leaves the
4985    /// declaration invisible (#2011). With no compile coverage nothing decides
4986    /// a platform macro, so the cross-file rule stands: the donor resolves its
4987    /// own conditionals and only has to stay free of contradiction with the
4988    /// reference. Otherwise a libuv-shaped `#if defined(__linux__)` prototype
4989    /// in a shared header is hidden from every unguarded caller in another
4990    /// translation unit (#2988).
4991    fn foreign_callable_declaration_activation(
4992        &self,
4993        analyzer: &CppGraphSource<'_>,
4994        donor_syntax: &PreparedSyntaxTree,
4995        declaration: &CodeUnit,
4996        reference: &CallableReferenceContext<'_>,
4997    ) -> Option<usize> {
4998        let build_decides = !self.compile_context_is_absent(reference.file);
4999        let proven = self.compile_proven_guards(reference.file);
5000        let augmented;
5001        let active = match reference.guards() {
5002            Some(active) if !proven.is_empty() => {
5003                augmented = active.union(&proven).cloned().collect();
5004                Some(&augmented)
5005            }
5006            other => other,
5007        };
5008        nameable_callable_declaration_nodes(analyzer, donor_syntax, declaration)
5009            .into_iter()
5010            .filter(|node| {
5011                let Some(required) = callable_declaration_guard_requirements(
5012                    *node,
5013                    donor_syntax.source(),
5014                    reference,
5015                ) else {
5016                    return false;
5017                };
5018                if required.is_empty() {
5019                    return true;
5020                }
5021                if build_decides {
5022                    guard_requirements_hold_at_reference(&required, active)
5023                } else {
5024                    guards_compatible_at_reference(&required, reference.guards())
5025                }
5026            })
5027            .map(callable_declaration_activation_byte)
5028            .min()
5029    }
5030
5031    pub fn external_type_candidate_visible_at(
5032        &self,
5033        file: &ProjectFile,
5034        candidate: &CodeUnit,
5035        reference_byte: usize,
5036    ) -> bool {
5037        if candidate.source() == file {
5038            return true;
5039        }
5040        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5041            return false;
5042        };
5043        self.visible_identifier_candidates(file, candidate.identifier())
5044            .filter(|peer| same_logical_symbol(candidate, peer))
5045            .any(|peer| {
5046                peer.source() == file
5047                    || self
5048                        .include_activation_for_source(
5049                            self.cpp,
5050                            file,
5051                            prepared.as_ref(),
5052                            peer.source(),
5053                        )
5054                        .is_some_and(|activation| activation <= reference_byte)
5055            })
5056    }
5057
5058    pub fn external_type_declaration_visible_at(
5059        &self,
5060        file: &ProjectFile,
5061        candidate: &CodeUnit,
5062        reference_byte: usize,
5063    ) -> bool {
5064        if candidate.source() == file {
5065            return true;
5066        }
5067        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5068            return false;
5069        };
5070        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5071            .is_some_and(|activation| activation <= reference_byte)
5072    }
5073
5074    /// The preprocessor facts the build proves for a reference sited in
5075    /// `file` (#2011).
5076    ///
5077    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
5078    /// `Defined` fact, and a fact holds only when every compile configuration
5079    /// that governs the file agrees on it (intersection). The facts are
5080    /// strictly additive to the reference's active guard set: they can prove a
5081    /// required guard, but the guard check itself is never weakened and no
5082    /// implication is ever inferred from source text.
5083    ///
5084    /// A file with its own database entry answers from that entry alone
5085    /// (phase 1). A header takes its context from the translation units whose
5086    /// include closure reaches it, intersected across all of them (phase 2):
5087    /// the header is compiled once per including TU, so a fact holds for a
5088    /// header-sited reference only when every one of those compilations
5089    /// proves it. A reaching TU the database does not cover proves nothing,
5090    /// which empties the intersection. A file nothing covers or reaches has
5091    /// no facts and every check runs on source structure alone.
5092    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5093        if let Some(cached) = self
5094            .compile_proven_guard_cells
5095            .lock()
5096            .expect("C++ compile-proven guard cache poisoned")
5097            .get(file)
5098        {
5099            return Arc::clone(cached);
5100        }
5101        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5102            Some(names) => names,
5103            None => {
5104                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5105                let seed = translation_units.next().and_then(|translation_unit| {
5106                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5107                });
5108                match seed {
5109                    None => HashSet::default(),
5110                    Some(mut names) => {
5111                        for translation_unit in translation_units {
5112                            let Some(reached) = context_fact_names(
5113                                self.cpp.compile_contexts_for(&translation_unit),
5114                            ) else {
5115                                names.clear();
5116                                break;
5117                            };
5118                            names.retain(|name| reached.contains(name));
5119                            if names.is_empty() {
5120                                break;
5121                            }
5122                        }
5123                        names
5124                    }
5125                }
5126            }
5127        };
5128        let proven = Arc::new(
5129            names
5130                .into_iter()
5131                .map(PreprocessorGuard::Defined)
5132                .collect::<HashSet<_>>(),
5133        );
5134        self.compile_proven_guard_cells
5135            .lock()
5136            .expect("C++ compile-proven guard cache poisoned")
5137            .insert(file.clone(), Arc::clone(&proven));
5138        proven
5139    }
5140
5141    /// The rule a conditional `#include` path's guards face on the way to a
5142    /// reference in `file`.
5143    ///
5144    /// This is the include-edge analogue of the choice
5145    /// [`Self::foreign_callable_declaration_activation`] makes for a foreign
5146    /// declaration's own guards (#2988), and it is decided by the same thing:
5147    /// what settles the reference's configuration. Memoized per file because
5148    /// the answer depends on nothing else, and the callers ask it once per
5149    /// candidate.
5150    fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5151        if let Some(cached) = self
5152            .include_path_admission_cells
5153            .lock()
5154            .expect("C++ include-path admission cache poisoned")
5155            .get(file)
5156            .copied()
5157        {
5158            return cached;
5159        }
5160        let admission = if self.compile_context_is_absent(file) {
5161            IncludePathAdmission::Compatible
5162        } else {
5163            IncludePathAdmission::Proven
5164        };
5165        self.include_path_admission_cells
5166            .lock()
5167            .expect("C++ include-path admission cache poisoned")
5168            .insert(file.clone(), admission);
5169        admission
5170    }
5171
5172    /// Whether no compile data covers the compilations of `file`: it has no
5173    /// database entry of its own, and either nothing reaches it or some
5174    /// translation unit that reaches it has no entry. This is the state a
5175    /// regenerated `compile_commands.json` could decide; data that is present
5176    /// for every governing compilation but does not prove a guard is a
5177    /// decided conservative miss, not this state.
5178    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5179        if !self.cpp.compile_contexts_for(file).is_empty() {
5180            return false;
5181        }
5182        let translation_units = self.cpp.reaching_translation_units(file);
5183        translation_units.is_empty()
5184            || translation_units
5185                .iter()
5186                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5187    }
5188
5189    /// Whether a lookup miss for `identifier` in `file` is explainable by
5190    /// missing compile context (#2011): some same-name declaration is
5191    /// reachable through a conditional include whose required guards neither
5192    /// contradict the reference's active guards nor follow from them, and the
5193    /// translation unit has no compile-commands entry that could decide the
5194    /// question. Callers surface this as an explicit "requires compile
5195    /// context" incompleteness instead of an indistinguishable miss.
5196    ///
5197    /// A structurally disproven declaration (contradicting guards) and a TU
5198    /// whose compile context exists but does not prove the guard both answer
5199    /// `false`: those misses are decided, not incomplete.
5200    pub fn miss_requires_compile_context(
5201        &self,
5202        file: &ProjectFile,
5203        identifier: &str,
5204        reference: Node<'_>,
5205    ) -> bool {
5206        if !self.compile_context_is_absent(file) {
5207            return false;
5208        }
5209        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5210            return false;
5211        };
5212        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5213        let reference_byte = reference.start_byte();
5214        let mut sources = self
5215            .visible_identifier_candidates(file, identifier)
5216            .map(CodeUnit::source)
5217            .filter(|source| *source != file)
5218            .collect::<Vec<_>>();
5219        sources.sort();
5220        sources.dedup();
5221        sources.into_iter().any(|declaration_source| {
5222            self.conditional_include_projections_for_source(
5223                file,
5224                prepared.as_ref(),
5225                declaration_source,
5226            )
5227            .iter()
5228            .any(|projection| {
5229                projection.activation_byte <= reference_byte
5230                    && !guard_requirements_hold_at_reference(
5231                        &projection.required_guards,
5232                        reference_guards.as_ref(),
5233                    )
5234                    && guards_compatible_at_reference(
5235                        &projection.required_guards,
5236                        reference_guards.as_ref(),
5237                    )
5238            })
5239        })
5240    }
5241
5242    /// Decide whether a declaration that lives in another file reaches a
5243    /// reference in `file`.
5244    ///
5245    /// An external header selects its declaration branch before the reference
5246    /// file is parsed. Require compatible reference guards, but do not test
5247    /// the header's guard expression for stability in the reference file: a
5248    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
5249    /// wraps every declaration of a portable C header, and demanding it would
5250    /// hide the whole header. Guards that the reference file imposes on its
5251    /// own `#include` still have to be stable, and must satisfy the rule
5252    /// [`IncludePathAdmission`] names for this reference's translation unit.
5253    fn foreign_declaration_reachable_at_reference(
5254        &self,
5255        file: &ProjectFile,
5256        prepared: &PreparedSyntaxTree,
5257        declaration_source: &ProjectFile,
5258        declaration_guards: &HashSet<PreprocessorGuard>,
5259        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5260        reference_byte: usize,
5261    ) -> bool {
5262        // The translation unit's build-proven defines join the reference's
5263        // active guard set (#2011): a conditional include like the nng
5264        // `NNG_PLATFORM_POSIX` chain is provable only by the compile command.
5265        // A reference whose own environment is unknown stays unknown -- the
5266        // facts extend an environment, they never invent one.
5267        let proven = self.compile_proven_guards(file);
5268        let augmented;
5269        let reference_guards = match reference_guards {
5270            Some(active) if !proven.is_empty() => {
5271                augmented = active.union(&proven).cloned().collect();
5272                Some(&augmented)
5273            }
5274            other => other,
5275        };
5276        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5277            eprintln!(
5278                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5279                declaration_source.rel_path().display(),
5280            );
5281        }
5282        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5283            return false;
5284        }
5285        if self
5286            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5287            .is_some_and(|activation| activation <= reference_byte)
5288        {
5289            return true;
5290        }
5291        let projections =
5292            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5293        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5294            eprintln!(
5295                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5296                declaration_source.rel_path().display(),
5297                declaration_guards.len(),
5298                proven.len(),
5299                projections.len(),
5300            );
5301        }
5302        let admission = self.include_path_admission(file);
5303        projections.iter().any(|projection| {
5304            projection.activation_byte <= reference_byte
5305                && admission.admits(
5306                    &projection.required_guards,
5307                    &projection.partial_guards,
5308                    reference_guards,
5309                )
5310                && self.preprocessor_guards_stable_between(
5311                    file,
5312                    projection.activation_byte,
5313                    reference_byte,
5314                    &projection.required_guards,
5315                )
5316        })
5317    }
5318
5319    fn foreign_declaration_may_be_reachable_from_raw_guards(
5320        &self,
5321        file: &ProjectFile,
5322        prepared: &PreparedSyntaxTree,
5323        declaration_source: &ProjectFile,
5324        declaration_guards: &HashSet<PreprocessorGuard>,
5325        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5326        reference_byte: usize,
5327    ) -> bool {
5328        let proven = self.compile_proven_guards(file);
5329        let augmented;
5330        let reference_guards = match reference_guards {
5331            Some(active) if !proven.is_empty() => {
5332                augmented = active.union(&proven).cloned().collect();
5333                Some(&augmented)
5334            }
5335            other => other,
5336        };
5337        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5338            return false;
5339        }
5340        if self
5341            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5342            .is_some_and(|activation| activation <= reference_byte)
5343        {
5344            return true;
5345        }
5346        let reachable = find_conditional_include_projection_for_source(
5347            self.cpp,
5348            self.token,
5349            file,
5350            prepared,
5351            declaration_source,
5352            self.include_path_admission(file),
5353            reference_guards,
5354            reference_byte,
5355            &|| {
5356                #[cfg(any(test, feature = "test-support"))]
5357                self.conditional_include_target_state_count
5358                    .fetch_add(1, Ordering::Relaxed);
5359            },
5360        );
5361        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5362            eprintln!(
5363                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5364                declaration_source.rel_path().display(),
5365                declaration_guards.len(),
5366                proven.len(),
5367                reference_guards.map_or(0, HashSet::len),
5368            );
5369        }
5370        reachable
5371    }
5372
5373    fn foreign_declaration_reachable_from_compile_proven_guards(
5374        &self,
5375        file: &ProjectFile,
5376        prepared: &PreparedSyntaxTree,
5377        declaration_source: &ProjectFile,
5378        declaration_guards: &HashSet<PreprocessorGuard>,
5379        reference_byte: usize,
5380    ) -> bool {
5381        let proven = self.compile_proven_guards(file);
5382        if proven.is_empty()
5383            || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5384        {
5385            return false;
5386        }
5387        let projections =
5388            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5389        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5390            eprintln!(
5391                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5392                declaration_source.rel_path().display(),
5393                declaration_guards.len(),
5394                proven.len(),
5395                projections.len(),
5396            );
5397        }
5398        projections.iter().any(|projection| {
5399            projection.activation_byte <= reference_byte
5400                    && guard_requirements_hold_at_reference(
5401                        &projection.required_guards,
5402                        Some(proven.as_ref()),
5403                    )
5404                    // Build facts hold at translation-unit entry. A source
5405                    // `#undef` or an earlier include may invalidate one before
5406                    // this conditional include is reached; mutations after the
5407                    // include cannot revoke declarations it already supplied.
5408                    && self.preprocessor_guards_stable_between(
5409                        file,
5410                        0,
5411                        projection.activation_byte,
5412                        &projection.required_guards,
5413                    )
5414        })
5415    }
5416
5417    pub fn external_type_candidate_visible_in_context(
5418        &self,
5419        analyzer: &CppGraphSource<'_>,
5420        file: &ProjectFile,
5421        candidate: &CodeUnit,
5422        reference: Node<'_>,
5423    ) -> bool {
5424        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5425        if report_stats {
5426            eprintln!(
5427                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5428                candidate.fq_name(),
5429                candidate.source().rel_path().display(),
5430                file.rel_path().display(),
5431                reference.start_byte(),
5432            );
5433        }
5434        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5435            return false;
5436        };
5437        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5438        let reference_guards = OnceCell::new();
5439        let reference_guards_at_site = || {
5440            reference_guards.get_or_init(|| {
5441                let started = Instant::now();
5442                if report_stats {
5443                    eprintln!(
5444                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5445                        file.rel_path().display(),
5446                        reference.start_byte(),
5447                        raw_reference_guards.as_ref().map_or(0, HashSet::len),
5448                    );
5449                }
5450                let macro_environment = self.macro_environment(file, reference.start_byte());
5451                let filtered = raw_reference_guards
5452                    .clone()
5453                    .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5454                if report_stats {
5455                    eprintln!(
5456                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5457                        filtered.is_some(),
5458                        started.elapsed().as_millis(),
5459                    );
5460                }
5461                filtered
5462            })
5463        };
5464
5465        let peers = self
5466            .visible_identifier_candidates(file, candidate.identifier())
5467            .filter(|peer| same_logical_symbol(candidate, peer))
5468            .collect::<Vec<_>>();
5469        if report_stats {
5470            let peer_sources = peers
5471                .iter()
5472                .map(|peer| peer.source().rel_path().display().to_string())
5473                .collect::<Vec<_>>();
5474            eprintln!(
5475                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5476                candidate.fq_name(),
5477            );
5478        }
5479        let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5480            declaration_guard_requirements(analyzer, self.cpp, peer)
5481                .into_iter()
5482                .any(|(declaration_byte, declaration_guards)| {
5483                    if peer.source() == file {
5484                        let visible = declaration_byte < reference.start_byte()
5485                            && declaration_guards.is_empty();
5486                        if report_stats {
5487                            eprintln!(
5488                                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5489                                peer.source().rel_path().display(),
5490                                declaration_guards.len(),
5491                            );
5492                        }
5493                        return visible;
5494                    }
5495                    let direct = declaration_guards.is_empty()
5496                        && self
5497                            .include_activation_for_source(
5498                                self.cpp,
5499                                file,
5500                                prepared.as_ref(),
5501                                peer.source(),
5502                            )
5503                            .is_some_and(|activation| activation <= reference.start_byte());
5504                    let compile_proven = !direct
5505                        && self.foreign_declaration_reachable_from_compile_proven_guards(
5506                            file,
5507                            prepared.as_ref(),
5508                            peer.source(),
5509                            &declaration_guards,
5510                            reference.start_byte(),
5511                        );
5512                    if report_stats {
5513                        eprintln!(
5514                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5515                            peer.source().rel_path().display(),
5516                            declaration_guards.len(),
5517                        );
5518                    }
5519                    direct || compile_proven
5520                })
5521        });
5522        if directly_visible_without_reference_environment {
5523            if report_stats {
5524                eprintln!(
5525                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5526                    candidate.fq_name(),
5527                );
5528            }
5529            return true;
5530        }
5531        let directly_visible = peers.iter().any(|peer| {
5532            declaration_guard_sites(analyzer, self.cpp, peer, Some(reference.start_byte()))
5533                .into_iter()
5534                .any(|site| {
5535                    let declaration_byte = site.byte;
5536                    let declaration_guards = site.guards;
5537                    if peer.source() == file {
5538                        if declaration_byte >= reference.start_byte() {
5539                            return false;
5540                        }
5541                        // The reference's own conditional context must either
5542                        // prove the declaration's requirements, or follow a
5543                        // completed family that encloses the declaration
5544                        // (#3297); either way it must not contradict them.
5545                        let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5546                            guard_requirements_hold_at_reference(&declaration_guards, guards)
5547                                || (site.completed_family_before_reference
5548                                    && guards_compatible_at_reference(&declaration_guards, guards))
5549                        };
5550                        if !active(raw_reference_guards.as_ref()) {
5551                            return false;
5552                        }
5553                        return active(reference_guards_at_site().as_ref())
5554                            && self.preprocessor_guards_stable_between(
5555                                file,
5556                                declaration_byte,
5557                                reference.start_byte(),
5558                                &declaration_guards,
5559                            );
5560                    }
5561                    let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5562                        file,
5563                        prepared.as_ref(),
5564                        peer.source(),
5565                        &declaration_guards,
5566                        raw_reference_guards.as_ref(),
5567                        reference.start_byte(),
5568                    );
5569                    if report_stats {
5570                        eprintln!(
5571                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5572                            peer.source().rel_path().display(),
5573                            declaration_guards.len(),
5574                        );
5575                    }
5576                    if !raw_feasible {
5577                        return false;
5578                    }
5579                    self.foreign_declaration_reachable_at_reference(
5580                        file,
5581                        prepared.as_ref(),
5582                        peer.source(),
5583                        &declaration_guards,
5584                        reference_guards_at_site().as_ref(),
5585                        reference.start_byte(),
5586                    )
5587                })
5588        });
5589        if directly_visible {
5590            if report_stats {
5591                eprintln!(
5592                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5593                    candidate.fq_name(),
5594                );
5595            }
5596            return true;
5597        }
5598        let complementary = self
5599            .visible_identifier_candidates(file, candidate.identifier())
5600            .filter(|peer| {
5601                peer.kind() == candidate.kind()
5602                    && peer.fq_name() == candidate.fq_name()
5603                    && peer.source() == candidate.source()
5604            })
5605            .collect::<Vec<_>>();
5606        // A completed #if/#else family declares the shared source-level name
5607        // before this reference. A later macro mutation cannot revoke that
5608        // declaration. The family gate below rejects declarations split across
5609        // separate conditional blocks, where mutation can change coverage.
5610        let complementary_family =
5611            self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5612        let raw_candidate_branch_compatible = complementary_family
5613            && raw_reference_guards.as_ref().is_some_and(|active| {
5614                declaration_guard_requirements(analyzer, self.cpp, candidate)
5615                    .iter()
5616                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5617            });
5618        if report_stats {
5619            eprintln!(
5620                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5621                candidate.fq_name(),
5622                complementary.len(),
5623                complementary_family,
5624                raw_candidate_branch_compatible,
5625            );
5626        }
5627        let candidate_branch_compatible = raw_candidate_branch_compatible
5628            && reference_guards_at_site().as_ref().is_some_and(|active| {
5629                declaration_guard_requirements(analyzer, self.cpp, candidate)
5630                    .iter()
5631                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5632            });
5633        let complementary_visible = candidate_branch_compatible
5634            && if candidate.source() == file {
5635                declaration_guard_requirements(analyzer, self.cpp, candidate)
5636                    .iter()
5637                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5638            } else {
5639                self.include_activation_for_source(
5640                    self.cpp,
5641                    file,
5642                    prepared.as_ref(),
5643                    candidate.source(),
5644                )
5645                .is_some_and(|activation| activation <= reference.start_byte())
5646            };
5647        if report_stats {
5648            eprintln!(
5649                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5650                if complementary_visible {
5651                    "complementary"
5652                } else {
5653                    "missing"
5654                },
5655                candidate.fq_name(),
5656            );
5657        }
5658        complementary_visible
5659    }
5660
5661    pub fn is_exhaustive_same_fqn_type_declaration_family(
5662        &self,
5663        analyzer: &CppGraphSource<'_>,
5664        file: &ProjectFile,
5665        candidate: &CodeUnit,
5666    ) -> bool {
5667        let candidates = self
5668            .visible_identifier_candidates(file, candidate.identifier())
5669            .filter(|peer| {
5670                peer.kind() == candidate.kind()
5671                    && peer.fq_name() == candidate.fq_name()
5672                    && peer.source() == candidate.source()
5673            })
5674            .collect::<Vec<_>>();
5675        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5676    }
5677
5678    /// Prove a nested type alias used as a dependent member-pointer owner when
5679    /// its owning class has mutually-exclusive declarations.  A common C++11
5680    /// compatibility shape provides the owning class in one preprocessor
5681    /// branch and aliases it to a standard-library type in the other branch;
5682    /// the nested fallback alias is therefore not itself active in every
5683    /// branch even though the qualified owner API is.
5684    ///
5685    /// This is deliberately narrower than ordinary type visibility.  The
5686    /// caller has already recovered a member-pointer owner path from the CST;
5687    /// this helper additionally requires the target's structured parent to
5688    /// match that path, physical source visibility, and exact preprocessor
5689    /// guard agreement with the parent declaration.  Only then may the
5690    /// parent's direct/complementary same-FQN visibility stand in for the
5691    /// nested terminal's active-branch check.
5692    pub fn dependent_member_pointer_alias_visible_in_context(
5693        &self,
5694        analyzer: &CppGraphSource<'_>,
5695        file: &ProjectFile,
5696        candidate: &CodeUnit,
5697        owner_components: &[String],
5698        reference: Node<'_>,
5699    ) -> bool {
5700        if !analyzer
5701            .type_alias_provider()
5702            .is_some_and(|provider| provider.is_type_alias(candidate))
5703        {
5704            return false;
5705        }
5706        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5707            return false;
5708        };
5709        if terminal != candidate.identifier()
5710            || canonical_cpp_scope_components(candidate) != owner_components
5711        {
5712            return false;
5713        }
5714        let Some(expected_parent_fq_name) =
5715            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5716        else {
5717            return false;
5718        };
5719        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5720            return false;
5721        };
5722        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5723            || parent_anchor.source() != candidate.source()
5724            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5725        {
5726            return false;
5727        }
5728
5729        // The ordinary path already handles unguarded aliases (and preserves
5730        // same-file declaration ordering).  This fallback is only for a
5731        // physically visible declaration whose guard is the owning branch's
5732        // guard, so reject a same-file declaration that appears after the
5733        // reference before considering guard compatibility.
5734        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5735            || candidate.source() == file
5736                && !analyzer
5737                    .ranges(candidate)
5738                    .iter()
5739                    .any(|range| range.start_byte < reference.start_byte())
5740        {
5741            return false;
5742        }
5743
5744        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5745        if candidate_guards.is_empty() {
5746            return false;
5747        }
5748        let same_guard_sets =
5749            |left: &[(usize, HashSet<PreprocessorGuard>)],
5750             right: &[(usize, HashSet<PreprocessorGuard>)]| {
5751                left.iter().all(|(_, left_guards)| {
5752                    right
5753                        .iter()
5754                        .any(|(_, right_guards)| left_guards == right_guards)
5755                })
5756            };
5757        let parent_candidates = self
5758            .visible_identifier_candidates(file, parent_anchor.identifier())
5759            .filter(|peer| {
5760                peer.kind() == parent_anchor.kind()
5761                    && peer.fq_name() == expected_parent_fq_name.as_str()
5762                    && peer.source() == parent_anchor.source()
5763                    && canonical_cpp_scope_components(peer) == owner_prefix
5764            })
5765            .filter_map(|peer| {
5766                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5767                (candidate_guards.len() == parent_guards.len()
5768                    && same_guard_sets(&candidate_guards, &parent_guards)
5769                    && same_guard_sets(&parent_guards, &candidate_guards))
5770                .then(|| (peer.clone(), parent_guards))
5771            })
5772            .collect::<Vec<_>>();
5773        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5774            return false;
5775        };
5776
5777        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5778            return false;
5779        };
5780        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5781        else {
5782            return false;
5783        };
5784        // An external header selects its declaration branch before the
5785        // reference file is parsed. Require compatible reference guards, but
5786        // do not test the header's guard expression for stability in the
5787        // reference file. Same-file aliases still require that stability.
5788        if !candidate_guards.iter().any(|(_, target_guards)| {
5789            guards_compatible_at_reference(target_guards, Some(&reference_guards))
5790                && (candidate.source() != file
5791                    || self.preprocessor_guards_stable_between(
5792                        file,
5793                        0,
5794                        reference.start_byte(),
5795                        target_guards,
5796                    ))
5797        }) {
5798            return false;
5799        }
5800
5801        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5802    }
5803
5804    /// Check a type candidate's preprocessor/import context without imposing
5805    /// ordinary declaration-before-reference ordering for same-file peers.
5806    ///
5807    /// C++ class scope makes member names visible throughout the complete
5808    /// class, including a trailing return type that appears before the member
5809    /// alias declaration in source order. Callers must first prove that the
5810    /// reference is inside the candidate's indexed class owner; this helper
5811    /// only relaxes the byte-order predicate while retaining guard and include
5812    /// activation checks.
5813    pub fn external_type_candidate_guard_compatible_in_context(
5814        &self,
5815        analyzer: &CppGraphSource<'_>,
5816        file: &ProjectFile,
5817        candidate: &CodeUnit,
5818        reference: Node<'_>,
5819    ) -> bool {
5820        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5821            return false;
5822        };
5823        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5824
5825        self.visible_identifier_candidates(file, candidate.identifier())
5826            .filter(|peer| same_logical_symbol(candidate, peer))
5827            .any(|peer| {
5828                declaration_guard_requirements(analyzer, self.cpp, peer)
5829                    .into_iter()
5830                    .any(|(declaration_byte, declaration_guards)| {
5831                        if peer.source() == file {
5832                            let (start, end) = if declaration_byte <= reference.start_byte() {
5833                                (declaration_byte, reference.start_byte())
5834                            } else {
5835                                (reference.start_byte(), declaration_byte)
5836                            };
5837                            return guard_requirements_hold_at_reference(
5838                                &declaration_guards,
5839                                reference_guards.as_ref(),
5840                            ) && self.preprocessor_guards_stable_between(
5841                                file,
5842                                start,
5843                                end,
5844                                &declaration_guards,
5845                            );
5846                        }
5847                        self.foreign_declaration_reachable_at_reference(
5848                            file,
5849                            prepared.as_ref(),
5850                            peer.source(),
5851                            &declaration_guards,
5852                            reference_guards.as_ref(),
5853                            reference.start_byte(),
5854                        )
5855                    })
5856            })
5857    }
5858
5859    /// Whether a same-file callable declaration is nameable from `reference`
5860    /// after deliberately relaxing declaration-before-reference ordering.
5861    ///
5862    /// Ordinary lookup still requires an earlier declaration. Definition
5863    /// navigation for incomplete C translation units may recover a later
5864    /// definition, but only when it is at file scope and its preprocessor
5865    /// requirements hold at the call (#2404).
5866    pub fn same_file_callable_guard_compatible_ignoring_order(
5867        &self,
5868        analyzer: &CppGraphSource<'_>,
5869        file: &ProjectFile,
5870        candidate: &CodeUnit,
5871        reference: Node<'_>,
5872    ) -> bool {
5873        if candidate.source() != file || !candidate.is_callable() {
5874            return false;
5875        }
5876        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5877            return false;
5878        };
5879        let guards = OnceCell::new();
5880        let context = CallableReferenceContext {
5881            file,
5882            position: Some(CallableReferencePosition {
5883                prepared: prepared.as_ref(),
5884                byte: reference.start_byte(),
5885                guards: &guards,
5886            }),
5887        };
5888        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
5889            .into_iter()
5890            .any(|declaration| {
5891                callable_preprocessor_context_is_visible_for_reference(
5892                    declaration,
5893                    prepared.source(),
5894                    &context,
5895                )
5896            })
5897    }
5898
5899    pub fn type_candidate_may_be_visible_before_reference(
5900        &self,
5901        analyzer: &CppGraphSource<'_>,
5902        file: &ProjectFile,
5903        candidate: &CodeUnit,
5904        reference_byte: usize,
5905    ) -> bool {
5906        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5907            return false;
5908        };
5909        let root = prepared.tree().root_node();
5910        let end_byte = reference_byte
5911            .saturating_add(1)
5912            .min(prepared.source().len());
5913        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5914            return false;
5915        };
5916        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5917    }
5918
5919    pub fn preprocessor_guards_stable_between(
5920        &self,
5921        file: &ProjectFile,
5922        start_byte: usize,
5923        end_byte: usize,
5924        guards: &HashSet<PreprocessorGuard>,
5925    ) -> bool {
5926        if guards.is_empty() || start_byte >= end_byte {
5927            return true;
5928        }
5929        let cell = self.macro_event_cell(file);
5930        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5931        let mut visited = HashSet::from_iter([file.clone()]);
5932        !events.iter().any(|event| {
5933            event.byte() >= start_byte
5934                && event.byte() < end_byte
5935                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5936        })
5937    }
5938
5939    fn macro_event_may_mutate_guards(
5940        &self,
5941        event: &MacroEvent,
5942        guards: &HashSet<PreprocessorGuard>,
5943        visited: &mut HashSet<ProjectFile>,
5944    ) -> bool {
5945        match event {
5946            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5947                guards.iter().any(|guard| guard.may_depend_on_macro(name))
5948            }
5949            MacroEvent::Include { targets, .. } => {
5950                targets.is_empty()
5951                    || targets
5952                        .iter()
5953                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
5954            }
5955            MacroEvent::Invalidate { .. } => true,
5956        }
5957    }
5958
5959    fn source_may_mutate_guards(
5960        &self,
5961        file: &ProjectFile,
5962        guards: &HashSet<PreprocessorGuard>,
5963        visited: &mut HashSet<ProjectFile>,
5964    ) -> bool {
5965        if !visited.insert(file.clone()) {
5966            return false;
5967        }
5968        let cell = self.macro_event_cell(file);
5969        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5970        events
5971            .iter()
5972            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5973    }
5974
5975    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5976        let normalized = normalize_reference_name(raw_name)?;
5977        self.type_candidates(file, &normalized)
5978            .into_iter()
5979            .next()
5980            .cloned()
5981    }
5982
5983    /// Mirror forward navigation's visible-name fallback for a bare parameter
5984    /// type after lexical owner and inheritance lookup is exhausted.
5985    ///
5986    /// Generated or otherwise unindexed base classes can hide the alias that
5987    /// makes a parameter type valid C++. Accept the fallback only when every
5988    /// include-visible class or alias with that spelling canonicalizes to one
5989    /// logical type. A shadowing local type resolves lexically before this
5990    /// path, while distinct visible types keep the result ambiguous.
5991    pub fn unique_visible_parameter_type_fallback(
5992        &self,
5993        analyzer: &CppGraphSource<'_>,
5994        file: &ProjectFile,
5995        node: Node<'_>,
5996        source: &str,
5997    ) -> Option<CodeUnit> {
5998        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5999            return None;
6000        }
6001        let name = node_text(node, source);
6002        let candidates = self
6003            .visible_identifier_candidates(file, name)
6004            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
6005            .filter(|candidate| {
6006                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
6007            })
6008            .collect::<Vec<_>>();
6009        self.unique_canonical_type_candidate(analyzer, file, &candidates)
6010    }
6011
6012    pub fn resolve_type_node_result(
6013        &self,
6014        file: &ProjectFile,
6015        node: Node<'_>,
6016        source: &str,
6017    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6018        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6019            return Ok(None);
6020        };
6021        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6022            return Ok(Some(primary));
6023        };
6024        self.resolve_template_arguments(file, primary, &arguments)
6025            .map(Some)
6026    }
6027
6028    pub fn resolve_type_node_primary(
6029        &self,
6030        file: &ProjectFile,
6031        node: Node<'_>,
6032        source: &str,
6033    ) -> Option<CodeUnit> {
6034        let components = cpp_type_name_components(node, source)?;
6035        self.resolve_type(file, &components.join("::"))
6036    }
6037
6038    pub fn resolve_template_arguments(
6039        &self,
6040        file: &ProjectFile,
6041        primary: CodeUnit,
6042        arguments: &[CppTemplateExpression],
6043    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6044        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6045    }
6046
6047    fn resolve_template_arguments_inner(
6048        &self,
6049        file: &ProjectFile,
6050        primary: CodeUnit,
6051        arguments: &[CppTemplateExpression],
6052        seen_aliases: &mut HashSet<CodeUnit>,
6053    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6054        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6055            && let Some(alias_target) = &metadata.alias_target
6056        {
6057            if !seen_aliases.insert(primary.clone()) {
6058                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6059            }
6060            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6061                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6062            let target_name = alias_target.components.join("::");
6063            let target_primary = if alias_target.global {
6064                unique_logical_type_candidate(self.type_candidates(file, &target_name))
6065            } else {
6066                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6067            };
6068            let Some(target_primary) = target_primary else {
6069                // A dependent or external RHS cannot be canonicalized from the
6070                // indexed graph. Preserve the alias's direct identity instead
6071                // of inventing a target from its source spelling.
6072                return Ok(primary);
6073            };
6074            let Some(target_arguments) = &alias_target.arguments else {
6075                return Ok(target_primary);
6076            };
6077            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6078                .ok_or(CppTemplateResolutionError::Substitution)?;
6079            return self.resolve_template_arguments_inner(
6080                file,
6081                target_primary,
6082                &target_arguments,
6083                seen_aliases,
6084            );
6085        }
6086
6087        let primary_fq_name = self
6088            .cpp_template_metadata
6089            .get(&primary)
6090            .map(|metadata| metadata.primary_fq_name.clone())
6091            .unwrap_or_else(|| primary.fq_name());
6092        let has_specialization_metadata = self
6093            .cpp_template_families
6094            .get(&primary_fq_name)
6095            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6096        if !has_specialization_metadata {
6097            return Ok(primary);
6098        }
6099        self.select_template_specialization(file, &primary, arguments)
6100    }
6101
6102    fn select_template_specialization(
6103        &self,
6104        file: &ProjectFile,
6105        resolved: &CodeUnit,
6106        explicit_arguments: &[CppTemplateExpression],
6107    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6108        let primary_fq_name = self
6109            .cpp_template_metadata
6110            .get(resolved)
6111            .map(|metadata| metadata.primary_fq_name.clone())
6112            .unwrap_or_else(|| resolved.fq_name());
6113        let family = self
6114            .cpp_template_families
6115            .get(&primary_fq_name)
6116            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6117        let primary_candidates = family
6118            .iter()
6119            .filter_map(|unit| {
6120                let metadata = self.cpp_template_metadata.get(unit)?;
6121                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6122            })
6123            .collect::<Vec<_>>();
6124        let primary_unit = primary_candidates
6125            .iter()
6126            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6127            .or_else(|| {
6128                primary_candidates
6129                    .iter()
6130                    .map(|(unit, _)| *unit)
6131                    .min_by_key(|unit| {
6132                        (
6133                            unit.source().to_string(),
6134                            unit.signature().unwrap_or_default(),
6135                        )
6136                    })
6137            })
6138            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6139        let primary_parameters =
6140            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6141                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6142        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6143            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6144
6145        let mut applicable = Vec::new();
6146        for unit in family {
6147            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6148                continue;
6149            };
6150            if metadata.is_primary() || !self.is_visible(file, unit) {
6151                continue;
6152            }
6153            if !cpp_specialization_matches(metadata, &expanded) {
6154                continue;
6155            }
6156            applicable.push((unit, metadata));
6157        }
6158        if applicable.is_empty() {
6159            return Ok(primary_unit.clone());
6160        }
6161
6162        // A scalar constraint count cannot represent C++ partial ordering:
6163        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
6164        // Select only a logical candidate whose structural pattern is strictly
6165        // more specialized than every other distinct applicable candidate.
6166        let winners = applicable
6167            .iter()
6168            .filter(|(candidate, candidate_metadata)| {
6169                applicable.iter().all(|(other, other_metadata)| {
6170                    same_visible_symbol(candidate, other)
6171                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6172                })
6173            })
6174            .copied()
6175            .collect::<Vec<_>>();
6176        let Some((selected, _)) = winners.first() else {
6177            // Mutually incomparable applicable candidates: every one of them
6178            // is a live contender.
6179            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6180                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6181            });
6182        };
6183        if winners
6184            .iter()
6185            .any(|(unit, _)| !same_visible_symbol(unit, selected))
6186        {
6187            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6188                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6189            });
6190        }
6191        Ok((*selected).clone())
6192    }
6193
6194    pub fn resolve_type_components_lexically(
6195        &self,
6196        analyzer: &CppGraphSource<'_>,
6197        file: &ProjectFile,
6198        components: &[String],
6199        global: bool,
6200        lexical_scope: &[String],
6201    ) -> LexicalTypeResolution {
6202        self.resolve_type_components_lexically_inner(
6203            analyzer,
6204            file,
6205            components,
6206            global,
6207            lexical_scope,
6208            TypeCandidateResolution::Canonical,
6209        )
6210    }
6211
6212    pub fn resolve_type_components_lexically_for_forward(
6213        &self,
6214        analyzer: &CppGraphSource<'_>,
6215        file: &ProjectFile,
6216        components: &[String],
6217        global: bool,
6218        lexical_scope: &[String],
6219    ) -> LexicalTypeResolution {
6220        self.resolve_type_components_lexically_inner(
6221            analyzer,
6222            file,
6223            components,
6224            global,
6225            lexical_scope,
6226            TypeCandidateResolution::PreserveAlias,
6227        )
6228    }
6229
6230    pub fn resolve_type_components_lexically_for_target(
6231        &self,
6232        analyzer: &CppGraphSource<'_>,
6233        file: &ProjectFile,
6234        components: &[String],
6235        global: bool,
6236        lexical_scope: &[String],
6237        target: &CodeUnit,
6238    ) -> LexicalTypeResolution {
6239        #[cfg(any(test, feature = "test-support"))]
6240        self.target_preserving_type_resolution_count
6241            .fetch_add(1, Ordering::Relaxed);
6242        self.resolve_type_components_lexically_inner(
6243            analyzer,
6244            file,
6245            components,
6246            global,
6247            lexical_scope,
6248            TypeCandidateResolution::PreserveTarget(target),
6249        )
6250    }
6251
6252    pub fn coarse_unqualified_type_reference_may_resolve(
6253        &self,
6254        file: &ProjectFile,
6255        name: &str,
6256    ) -> bool {
6257        if name.is_empty() {
6258            return true;
6259        }
6260        self.visible_identifier_candidates(file, name)
6261            .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
6262            || self.visible_parser_alias_name_is_visible(file, name)
6263    }
6264
6265    #[allow(clippy::too_many_arguments)]
6266    pub fn structured_type_reference_may_resolve_to_target(
6267        &self,
6268        analyzer: &CppGraphSource<'_>,
6269        file: &ProjectFile,
6270        components: &[String],
6271        global: bool,
6272        lexical_scope: &[String],
6273        target: &CodeUnit,
6274    ) -> bool {
6275        if components.is_empty() {
6276            return true;
6277        }
6278        let Some(terminal) = components.last() else {
6279            return true;
6280        };
6281        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6282            .map(|qualified| qualified.join("::"))
6283            .collect::<Vec<_>>();
6284        let target_name = cpp_name_for(target);
6285        if qualified_tiers
6286            .iter()
6287            .any(|qualified| qualified == &target_name)
6288        {
6289            return true;
6290        }
6291
6292        let mut saw_shape_candidate = false;
6293        for candidate in self.visible_identifier_candidates(file, terminal) {
6294            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6295            {
6296                continue;
6297            }
6298            let candidate_name = cpp_name_for(candidate);
6299            let shape_matches = if global || components.len() > 1 {
6300                qualified_tiers
6301                    .iter()
6302                    .any(|qualified| qualified == &candidate_name)
6303            } else {
6304                true
6305            };
6306            if !shape_matches {
6307                continue;
6308            }
6309            saw_shape_candidate = true;
6310            if same_visible_symbol(candidate, target)
6311                || self.c_tag_declaration_family_matches_target(
6312                    analyzer,
6313                    file,
6314                    std::slice::from_ref(&candidate),
6315                    target,
6316                )
6317                || self.compatible_primary_template_redeclarations(candidate, target)
6318                || (declared_type_alias(analyzer, candidate)
6319                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6320            {
6321                return true;
6322            }
6323        }
6324
6325        !saw_shape_candidate
6326    }
6327
6328    /// A C tag's forward declaration and complete definition are one logical
6329    /// type even when their indexed signatures and source files differ. Keep
6330    /// this identity narrow: the complete declaration must be a top-level C
6331    /// tag, every visible candidate must be its physical forward declaration,
6332    /// and the parsed tag kind must agree. Different FQNs, competing complete
6333    /// definitions, aliases, and struct/union mismatches remain ambiguous.
6334    fn cached_c_tag_kind(
6335        &self,
6336        analyzer: &CppGraphSource<'_>,
6337        candidate: &CodeUnit,
6338    ) -> Option<CppCTagKind> {
6339        if let Some(kind) = self
6340            .c_tag_kind_cache
6341            .lock()
6342            .expect("C tag kind cache poisoned")
6343            .get(candidate)
6344        {
6345            return *kind;
6346        }
6347        let kind = indexed_c_tag_kind(analyzer, candidate);
6348        self.c_tag_kind_cache
6349            .lock()
6350            .expect("C tag kind cache poisoned")
6351            .insert(candidate.clone(), kind);
6352        kind
6353    }
6354
6355    fn cached_unique_c_tag_complete_definition(
6356        &self,
6357        analyzer: &CppGraphSource<'_>,
6358        target: &CodeUnit,
6359        target_tag: CppCTagKind,
6360    ) -> Option<CodeUnit> {
6361        if let Some(definition) = self
6362            .c_tag_complete_definition_cache
6363            .lock()
6364            .expect("C tag complete-definition cache poisoned")
6365            .get(target)
6366        {
6367            return definition.clone();
6368        }
6369        let complete_definitions = analyzer
6370            .definitions(&target.fq_name())
6371            .filter(|candidate| {
6372                candidate.is_class()
6373                    && !declared_type_alias(analyzer, candidate)
6374                    && is_c_source_file(candidate.source())
6375                    && analyzer.parent_of(candidate).is_none()
6376                    && cpp_class_declaration_strength(analyzer, candidate)
6377                        == CppClassDeclarationStrength::Full
6378                    && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6379            })
6380            .collect::<HashSet<_>>();
6381        let definition = (complete_definitions.len() == 1)
6382            .then(|| complete_definitions.into_iter().next())
6383            .flatten()
6384            .filter(|candidate| same_visible_symbol(candidate, target));
6385        self.c_tag_complete_definition_cache
6386            .lock()
6387            .expect("C tag complete-definition cache poisoned")
6388            .insert(target.clone(), definition.clone());
6389        definition
6390    }
6391
6392    pub fn c_tag_declaration_family_matches_target(
6393        &self,
6394        analyzer: &CppGraphSource<'_>,
6395        visible_from: &ProjectFile,
6396        candidates: &[&CodeUnit],
6397        target: &CodeUnit,
6398    ) -> bool {
6399        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6400            let candidate_evidence = candidates
6401                .iter()
6402                .map(|candidate| {
6403                    (
6404                        candidate.fq_name(),
6405                        candidate.source().rel_path().to_path_buf(),
6406                        cpp_class_declaration_strength(analyzer, candidate),
6407                        indexed_c_tag_kind(analyzer, candidate),
6408                        self.is_physically_visible(visible_from, candidate),
6409                    )
6410                })
6411                .collect::<Vec<_>>();
6412            eprintln!(
6413                "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6414                visible_from.rel_path().display(),
6415                target.fq_name(),
6416                target.source().rel_path().display(),
6417                cpp_class_declaration_strength(analyzer, target),
6418                indexed_c_tag_kind(analyzer, target),
6419            );
6420        }
6421        if candidates.is_empty()
6422            || !target.is_class()
6423            || declared_type_alias(analyzer, target)
6424            || !is_c_source_file(target.source())
6425            || analyzer.parent_of(target).is_some()
6426            || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6427        {
6428            return false;
6429        }
6430        let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6431            return false;
6432        };
6433        if self
6434            .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6435            .is_none()
6436        {
6437            return false;
6438        }
6439        let mut saw_visible_forward = false;
6440        for candidate in candidates.iter().copied() {
6441            if candidate == target {
6442                continue;
6443            }
6444            if !candidate.is_class()
6445                || declared_type_alias(analyzer, candidate)
6446                || candidate.fq_name() != target.fq_name()
6447                || analyzer.parent_of(candidate).is_some()
6448                || cpp_class_declaration_strength(analyzer, candidate)
6449                    != CppClassDeclarationStrength::Forward
6450                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6451                || !self.is_physically_visible(visible_from, candidate)
6452            {
6453                return false;
6454            }
6455            saw_visible_forward = true;
6456        }
6457        saw_visible_forward
6458    }
6459
6460    /// Collapse one visible complete C tag and its visible forward declarations
6461    /// before ordinary lexical resolution sees their different signatures as
6462    /// competing types. A second complete definition, a different FQN/tag
6463    /// kind, or an unknown declaration shape remains ambiguous.
6464    fn unique_c_tag_declaration_family(
6465        &self,
6466        analyzer: &CppGraphSource<'_>,
6467        visible_from: &ProjectFile,
6468        candidates: &[&CodeUnit],
6469    ) -> Option<CodeUnit> {
6470        let first = candidates.first()?;
6471        let target_fq_name = first.fq_name();
6472        let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6473        let mut full = None;
6474        let mut saw_forward = false;
6475        for candidate in candidates.iter().copied() {
6476            if !candidate.is_class()
6477                || declared_type_alias(analyzer, candidate)
6478                || candidate.fq_name() != target_fq_name
6479                || analyzer.parent_of(candidate).is_some()
6480                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6481            {
6482                return None;
6483            }
6484            match cpp_class_declaration_strength(analyzer, candidate) {
6485                CppClassDeclarationStrength::Full
6486                    if is_c_source_file(candidate.source())
6487                        && full.replace(candidate.clone()).is_none() => {}
6488                CppClassDeclarationStrength::Forward
6489                    if self.is_physically_visible(visible_from, candidate) =>
6490                {
6491                    saw_forward = true
6492                }
6493                _ => return None,
6494            }
6495        }
6496        if saw_forward { full } else { None }
6497    }
6498
6499    pub fn target_preserving_reference_namespace(
6500        &self,
6501        analyzer: &CppGraphSource<'_>,
6502        file: &ProjectFile,
6503        identifier: &str,
6504        target: &CodeUnit,
6505    ) -> Option<Vec<String>> {
6506        let mut namespace = None;
6507        for candidate in self.visible_identifier_candidates(file, identifier) {
6508            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6509            {
6510                continue;
6511            }
6512            if !(same_visible_symbol(candidate, target)
6513                || self.compatible_primary_template_redeclarations(candidate, target)
6514                || declared_type_alias(analyzer, candidate)
6515                    && self.structured_alias_primary_preserves_target(
6516                        analyzer, file, candidate, target,
6517                    ))
6518            {
6519                continue;
6520            }
6521            if namespace
6522                .as_ref()
6523                .is_some_and(|existing| existing != candidate.package_name())
6524            {
6525                return None;
6526            }
6527            namespace = Some(candidate.package_name().to_string());
6528        }
6529        let namespace = namespace?;
6530        Some(
6531            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6532                brokk_bifrost_core::analyzer::Language::Cpp,
6533                &namespace,
6534            ),
6535        )
6536    }
6537
6538    pub fn resolve_imported_type_candidate(
6539        &self,
6540        analyzer: &CppGraphSource<'_>,
6541        file: &ProjectFile,
6542        target: &CodeUnit,
6543        target_components: &[String],
6544        direct_target: Option<&CodeUnit>,
6545        preserve_alias: bool,
6546    ) -> LexicalTypeResolution {
6547        let candidates = [target];
6548        let resolution = if preserve_alias {
6549            TypeCandidateResolution::PreserveAlias
6550        } else {
6551            direct_target.map_or(
6552                TypeCandidateResolution::Canonical,
6553                TypeCandidateResolution::PreserveTarget,
6554            )
6555        };
6556        // One candidate goes in, so a failure here is never "choose one of
6557        // these": it is the alias chain leaving the index, which must answer
6558        // missing rather than ambiguous (#1828).
6559        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6560            Ok(unit) => LexicalTypeResolution::Resolved {
6561                unit,
6562                components: target_components.to_vec(),
6563                candidates: vec![target.clone()],
6564            },
6565            Err(failure) => failure.lexical_resolution(),
6566        }
6567    }
6568
6569    fn resolve_type_components_lexically_inner(
6570        &self,
6571        analyzer: &CppGraphSource<'_>,
6572        file: &ProjectFile,
6573        components: &[String],
6574        global: bool,
6575        lexical_scope: &[String],
6576        resolution: TypeCandidateResolution<'_>,
6577    ) -> LexicalTypeResolution {
6578        if components.is_empty() {
6579            return LexicalTypeResolution::Missing;
6580        }
6581        // A C++ class injects its own name into the class scope.  The indexed
6582        // FqName for that declaration is the class path itself (for example,
6583        // `n::raw_hash_set`), not a synthetic child named
6584        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
6585        // requested identifier to every scope component, so they cannot
6586        // represent that injected binding when the enclosing class is the
6587        // closest scope.  Recover the binding from the structured class path
6588        // before allowing lookup to fall through to an outer same-spelled
6589        // declaration.
6590        let mut injected = self.resolve_injected_class_name(
6591            analyzer,
6592            file,
6593            components,
6594            global,
6595            lexical_scope,
6596            resolution,
6597        );
6598        for qualified in lexical_component_tiers(components, global, lexical_scope) {
6599            let prefix_len = qualified.len().saturating_sub(components.len());
6600            if injected
6601                .as_ref()
6602                .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6603            {
6604                return injected
6605                    .take()
6606                    .expect("injected class resolution was just present")
6607                    .1;
6608            }
6609            let qualified_name = qualified.join("::");
6610            let candidates = self
6611                .type_candidates(file, &qualified_name)
6612                .into_iter()
6613                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6614                .collect::<Vec<_>>();
6615            if candidates.is_empty() {
6616                if !global && components.len() == 1 {
6617                    match self.resolve_inherited_type_for_lexical_scope(
6618                        analyzer,
6619                        file,
6620                        &qualified[..prefix_len],
6621                        &components[0],
6622                        resolution,
6623                    ) {
6624                        LexicalTypeResolution::Missing => {}
6625                        inherited => return inherited,
6626                    }
6627                }
6628                continue;
6629            }
6630            let candidates =
6631                self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6632            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6633                Ok(unit) => unit,
6634                Err(failure) => return failure.lexical_resolution(),
6635            };
6636            return LexicalTypeResolution::Resolved {
6637                unit,
6638                components: qualified,
6639                candidates: candidates.into_iter().cloned().collect(),
6640            };
6641        }
6642        LexicalTypeResolution::Missing
6643    }
6644
6645    fn resolve_injected_class_name(
6646        &self,
6647        analyzer: &CppGraphSource<'_>,
6648        file: &ProjectFile,
6649        components: &[String],
6650        global: bool,
6651        lexical_scope: &[String],
6652        resolution: TypeCandidateResolution<'_>,
6653    ) -> Option<(usize, LexicalTypeResolution)> {
6654        if global
6655            || components.len() != 1
6656            || file.rel_path().extension().is_some_and(|ext| ext == "c")
6657            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6658        {
6659            return None;
6660        }
6661        let name = components.first()?;
6662        let mut matches: Vec<&CodeUnit> = Vec::new();
6663        let mut owner_len = 0;
6664        for candidate in self.visible_identifier_candidates(file, name) {
6665            if !candidate.is_class()
6666                || declared_type_alias(analyzer, candidate)
6667                || candidate.identifier() != name
6668            {
6669                continue;
6670            }
6671            let candidate_scope = canonical_cpp_scope_components(candidate);
6672            if candidate_scope.len() > lexical_scope.len()
6673                || !lexical_scope.starts_with(&candidate_scope)
6674                || candidate_scope.last().is_none_or(|last| last != name)
6675            {
6676                continue;
6677            }
6678            if candidate_scope.len() > owner_len {
6679                owner_len = candidate_scope.len();
6680                matches.clear();
6681            }
6682            if candidate_scope.len() == owner_len
6683                && !matches
6684                    .iter()
6685                    .any(|existing| same_logical_symbol(existing, candidate))
6686            {
6687                matches.push(candidate);
6688            }
6689        }
6690        if matches.is_empty() {
6691            return None;
6692        }
6693        // The exact current class boundary is the most important injected-name
6694        // case. Ordinary lexical lookup appends the requested name and can
6695        // otherwise select a synthetic constructor-shaped child such as
6696        // `Portal::Impl::Impl` instead of the enclosing `Portal::Impl` class.
6697        // The global and single-component gates above keep qualified receiver
6698        // and static-qualifier contexts out of this recovery.
6699        let owner_components = lexical_scope[..owner_len].to_vec();
6700        let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6701        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6702            Ok(unit) => LexicalTypeResolution::Resolved {
6703                unit,
6704                components: owner_components,
6705                candidates: matches.into_iter().cloned().collect(),
6706            },
6707            Err(failure) => failure.lexical_resolution(),
6708        };
6709        Some((owner_len, resolution))
6710    }
6711
6712    fn resolve_inherited_type_for_lexical_scope(
6713        &self,
6714        analyzer: &CppGraphSource<'_>,
6715        file: &ProjectFile,
6716        lexical_scope: &[String],
6717        name: &str,
6718        resolution: TypeCandidateResolution<'_>,
6719    ) -> LexicalTypeResolution {
6720        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6721            return LexicalTypeResolution::Missing;
6722        };
6723        let lexical_owner_name = lexical_scope.join("::");
6724        if lexical_owner_name.is_empty() {
6725            return LexicalTypeResolution::Missing;
6726        }
6727        let owner_candidates = self
6728            .type_candidates(file, &lexical_owner_name)
6729            .into_iter()
6730            .filter(|candidate| {
6731                canonical_cpp_name_matches(candidate, &lexical_owner_name)
6732                    && !declared_type_alias(analyzer, candidate)
6733            })
6734            .collect::<Vec<_>>();
6735        if owner_candidates.is_empty() {
6736            return LexicalTypeResolution::Missing;
6737        }
6738        // A visible forward declaration and the physical class definition share
6739        // one FQN, but only the definition owns hierarchy facts. When lookup is
6740        // physically inside that definition, do not let an earlier header
6741        // forward declaration erase its base edges (#2240).
6742        let physical_owner_candidates = owner_candidates
6743            .iter()
6744            .copied()
6745            .filter(|candidate| candidate.source() == file)
6746            .collect::<Vec<_>>();
6747        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6748            owner_candidates
6749        } else {
6750            physical_owner_candidates
6751        };
6752        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6753            return LexicalTypeResolution::Ambiguous;
6754        };
6755
6756        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6757        let mut visited_owners = HashSet::default();
6758        while !frontier.is_empty() {
6759            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6760            let mut next_frontier = Vec::new();
6761            for owner in frontier {
6762                if !visited_owners.insert(owner.fq_name()) {
6763                    continue;
6764                }
6765                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6766                let candidates = self
6767                    .type_candidates(file, &qualified_name)
6768                    .into_iter()
6769                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6770                    .collect::<Vec<_>>();
6771                if candidates.is_empty() {
6772                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
6773                        if !next_frontier
6774                            .iter()
6775                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6776                        {
6777                            next_frontier.push(ancestor);
6778                        }
6779                    }
6780                    continue;
6781                }
6782                let candidates =
6783                    self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6784                let unit =
6785                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6786                        Ok(unit) => unit,
6787                        Err(failure) => return failure.lexical_resolution(),
6788                    };
6789                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6790            }
6791            if let Some((unit, candidates)) = level_matches.first().cloned() {
6792                let Some(first_declaration) = candidates.first() else {
6793                    return LexicalTypeResolution::Ambiguous;
6794                };
6795                if !level_matches.iter().all(|(_, declarations)| {
6796                    declarations
6797                        .iter()
6798                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
6799                }) {
6800                    return LexicalTypeResolution::Ambiguous;
6801                }
6802                let mut components = lexical_scope.to_vec();
6803                components.push(name.to_string());
6804                return LexicalTypeResolution::Resolved {
6805                    unit,
6806                    components,
6807                    candidates,
6808                };
6809            }
6810            frontier = next_frontier;
6811        }
6812        LexicalTypeResolution::Missing
6813    }
6814
6815    /// Resolve a base class through its injected class name at the nearest
6816    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
6817    ///
6818    /// A base whose canonical full definition cannot be pinned from `file` -
6819    /// a forward declaration the include closure completes with two different
6820    /// full definitions, or an alias chain that leaves the index - stops the
6821    /// walk only when that base is spelled `injected_name`. The mem-initializer
6822    /// names a base by that base's own injected class name, so a base spelled
6823    /// differently can never be the one it names, whichever definition it would
6824    /// have turned out to be; aborting the level on its account instead loses
6825    /// the sibling base that *is* named (#2543). A base that is spelled
6826    /// `injected_name` still fails closed, because choosing a deeper same-named
6827    /// ancestor over it would bind the initializer to the wrong constructor.
6828    /// The skipped base carries its own ancestors out of the walk with it: with
6829    /// no canonical unit, the repeated-base accounting below cannot tell one
6830    /// inherited path through it from two.
6831    pub fn inherited_injected_class_owner(
6832        &self,
6833        analyzer: &CppGraphSource<'_>,
6834        file: &ProjectFile,
6835        enclosing_owner: &CodeUnit,
6836        injected_name: &str,
6837    ) -> Option<CodeUnit> {
6838        let hierarchy = analyzer.type_hierarchy_provider()?;
6839        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6840        let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6841        while !frontier.is_empty() {
6842            let mut level_matches = Vec::new();
6843            let mut next_frontier = Vec::new();
6844            for raw_owner in frontier {
6845                let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6846                else {
6847                    if raw_owner.identifier() == injected_name {
6848                        return None;
6849                    }
6850                    continue;
6851                };
6852                let propagated = propagated_counts.entry(owner.clone()).or_default();
6853                if *propagated == 2 {
6854                    continue;
6855                }
6856                *propagated += 1;
6857                if owner.identifier() == injected_name {
6858                    level_matches.push(owner.clone());
6859                }
6860                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6861            }
6862            match level_matches.as_slice() {
6863                [owner] => return Some(owner.clone()),
6864                [_, ..] => return None,
6865                [] => {}
6866            }
6867            frontier = next_frontier;
6868        }
6869        None
6870    }
6871
6872    /// The one type the candidates name under `resolution`, or why they do not
6873    /// name one. The two preserving modes only ever reject candidates that
6874    /// disagree with each other, which is ambiguity; canonicalization can also
6875    /// fail because the alias chain leaves the index (#1828).
6876    fn resolve_type_candidates(
6877        &self,
6878        analyzer: &CppGraphSource<'_>,
6879        file: &ProjectFile,
6880        candidates: &[&CodeUnit],
6881        resolution: TypeCandidateResolution<'_>,
6882    ) -> Result<CodeUnit, TypeCandidateFailure> {
6883        if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6884            && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6885        {
6886            return Ok(unit);
6887        }
6888        match resolution {
6889            TypeCandidateResolution::Canonical => {
6890                self.canonical_type_candidate_resolution(analyzer, file, candidates)
6891            }
6892            TypeCandidateResolution::PreserveAlias => {
6893                // A generated index can retain identical alias spellings from
6894                // mutually exclusive headers. When the reference file
6895                // physically reaches exactly one of those source declarations,
6896                // include closure is the structured evidence that selects it;
6897                // treating the two source spellings as an overload set makes a
6898                // reachable alias appear ambiguous (#1844).
6899                let same_fqn_alias_family = candidates.len() > 1
6900                    && candidates.iter().all(|candidate| {
6901                        declared_type_alias(analyzer, candidate)
6902                            && same_logical_symbol(candidates[0], candidate)
6903                    })
6904                    && candidates
6905                        .iter()
6906                        .any(|candidate| candidate.source() != candidates[0].source());
6907                if same_fqn_alias_family {
6908                    let physically_visible = candidates
6909                        .iter()
6910                        .copied()
6911                        .filter(|candidate| self.is_physically_visible(file, candidate))
6912                        .collect::<Vec<_>>();
6913                    // The family is one logical declaration only when the
6914                    // reachable spellings agree. Two same-FQN aliases whose
6915                    // written targets differ (`using Choice = Canonical;` in
6916                    // one header, `using Choice = ::Canonical;` in another)
6917                    // are a genuine conflict, and choosing the first indexed
6918                    // one silently binds the reference to an arbitrary owner
6919                    // (#2398). Collapse only a single reachable declaration
6920                    // or reachable declarations with one structured target;
6921                    // everything else stays ambiguous below.
6922                    let one_structured_target = physically_visible.len() > 1
6923                        && physically_visible.iter().skip(1).all(|candidate| {
6924                            let target = self.structured_alias_target(analyzer, candidate);
6925                            target.is_some()
6926                                && target
6927                                    == self.structured_alias_target(analyzer, physically_visible[0])
6928                        });
6929                    if physically_visible.len() == 1 || one_structured_target {
6930                        return Ok(physically_visible[0].clone());
6931                    }
6932                }
6933                unique_type_candidate_preserving_alias(analyzer, file, candidates)
6934                    .ok_or(TypeCandidateFailure::Ambiguous)
6935            }
6936            TypeCandidateResolution::PreserveTarget(target) => self
6937                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6938                .ok_or(TypeCandidateFailure::Ambiguous),
6939        }
6940    }
6941
6942    fn candidates_for_type_resolution<'b>(
6943        &self,
6944        analyzer: &CppGraphSource<'_>,
6945        file: &ProjectFile,
6946        candidates: &[&'b CodeUnit],
6947        resolution: TypeCandidateResolution<'_>,
6948    ) -> Vec<&'b CodeUnit> {
6949        if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6950            let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6951            if compile_proven.len() == 1 {
6952                return compile_proven;
6953            }
6954        }
6955        candidates.to_vec()
6956    }
6957
6958    /// Narrow a same-name forward lookup to the declaration selected by the
6959    /// translation unit's compile command. The lexical resolver intentionally
6960    /// does not receive a reference node, so this is the only compile-context
6961    /// evidence available at that stage. A single selected candidate is safe:
6962    /// the caller still checks include activation and the reference's own
6963    /// guards before reporting the result as visible.
6964    fn compile_proven_type_candidates<'b>(
6965        &self,
6966        analyzer: &CppGraphSource<'_>,
6967        file: &ProjectFile,
6968        candidates: &[&'b CodeUnit],
6969    ) -> Vec<&'b CodeUnit> {
6970        let proven = self.compile_proven_guards(file);
6971        if proven.is_empty() {
6972            return Vec::new();
6973        }
6974        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6975            return Vec::new();
6976        };
6977        candidates
6978            .iter()
6979            .copied()
6980            .filter(|candidate| {
6981                let declaration_guards =
6982                    declaration_guard_requirements(analyzer, self.cpp, candidate);
6983                if declaration_guards.is_empty() {
6984                    return false;
6985                }
6986                if candidate.source() == file {
6987                    return declaration_guards.iter().any(|(_, required)| {
6988                        guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6989                    });
6990                }
6991                declaration_guards.iter().any(|(_, required)| {
6992                    self.foreign_declaration_reachable_from_compile_proven_guards(
6993                        file,
6994                        prepared.as_ref(),
6995                        candidate.source(),
6996                        required,
6997                        usize::MAX,
6998                    )
6999                })
7000            })
7001            .collect()
7002    }
7003
7004    pub fn resolve_callable_value_components_lexically(
7005        &self,
7006        analyzer: &CppGraphSource<'_>,
7007        file: &ProjectFile,
7008        owner_components: &[String],
7009        member_name: &str,
7010        global: bool,
7011        lexical_scope: &[String],
7012    ) -> LexicalCallableValueResolution {
7013        if owner_components.is_empty() || member_name.is_empty() {
7014            return LexicalCallableValueResolution::Missing;
7015        }
7016        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7017            let owner_name = qualified_owner.join("::");
7018            let type_candidates = self
7019                .type_candidates(file, &owner_name)
7020                .into_iter()
7021                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7022                .collect::<Vec<_>>();
7023            let resolved_type = if type_candidates.is_empty() {
7024                None
7025            } else {
7026                let Some(unit) =
7027                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7028                else {
7029                    return LexicalCallableValueResolution::Ambiguous;
7030                };
7031                Some(unit)
7032            };
7033
7034            let mut qualified_callable = qualified_owner;
7035            qualified_callable.push(member_name.to_string());
7036            let callable_name = qualified_callable.join("::");
7037            let free_function = self
7038                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7039                .into_iter()
7040                .find(|candidate| {
7041                    canonical_cpp_name_matches(candidate, &callable_name)
7042                        && type_owner_of(analyzer, candidate).is_none()
7043                })
7044                .cloned();
7045
7046            match (resolved_type, free_function) {
7047                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7048                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7049                (None, Some(function)) => {
7050                    return LexicalCallableValueResolution::FreeFunction(function);
7051                }
7052                (None, None) => {}
7053            }
7054        }
7055        LexicalCallableValueResolution::Missing
7056    }
7057
7058    fn resolve_type_for_declaration(
7059        &self,
7060        visible_from: &ProjectFile,
7061        declaration: &CodeUnit,
7062        raw_name: &str,
7063    ) -> Option<CodeUnit> {
7064        let normalized = normalize_reference_name(raw_name)?;
7065        if !normalized.contains("::")
7066            && let Some(namespace) = cpp_namespace_for(declaration)
7067        {
7068            for prefix in namespace_prefixes(&namespace) {
7069                let qualified = format!("{prefix}::{normalized}");
7070                if let Some(unit) = self
7071                    .type_candidates(visible_from, &qualified)
7072                    .into_iter()
7073                    .next()
7074                {
7075                    return Some(unit.clone());
7076                }
7077            }
7078        }
7079        self.resolve_type(visible_from, raw_name)
7080    }
7081
7082    fn resolve_unique_canonical_type_for_declaration(
7083        &self,
7084        analyzer: &CppGraphSource<'_>,
7085        visible_from: &ProjectFile,
7086        declaration: &CodeUnit,
7087        raw_name: &str,
7088    ) -> Option<CodeUnit> {
7089        let mut current = self.resolve_defining_type_for_declaration(
7090            analyzer,
7091            visible_from,
7092            declaration,
7093            raw_name,
7094        )?;
7095        let mut seen_aliases = HashSet::default();
7096        loop {
7097            let Some(target) = self.structured_alias_target(analyzer, &current) else {
7098                return current.is_class().then_some(current);
7099            };
7100            if matches!(target, StructuredAliasTarget::Builtin) {
7101                return current.is_class().then_some(current);
7102            }
7103            if !seen_aliases.insert(current.clone()) {
7104                return None;
7105            }
7106            current = self.resolve_structured_alias_target(visible_from, &current, &target)?;
7107        }
7108    }
7109
7110    pub fn canonical_type_unit(
7111        &self,
7112        analyzer: &CppGraphSource<'_>,
7113        visible_from: &ProjectFile,
7114        unit: &CodeUnit,
7115    ) -> Option<CodeUnit> {
7116        self.canonical_type_resolution(analyzer, visible_from, unit)
7117            .ok()
7118    }
7119
7120    /// Follow an alias only when it is visible at `reference`.
7121    ///
7122    /// The consumer need not spell the alias target. In particular, a
7123    /// conditional include can make `PublicPtr` visible at the reference while
7124    /// ordinary physical-include visibility is false. Prove the public alias
7125    /// with the reference's guard environment, then use the existing structured
7126    /// alias-chain resolver over the consumer's bounded declaration index.
7127    pub fn canonical_type_unit_in_context(
7128        &self,
7129        analyzer: &CppGraphSource<'_>,
7130        visible_from: &ProjectFile,
7131        reference: Node<'_>,
7132        unit: &CodeUnit,
7133    ) -> Option<CodeUnit> {
7134        if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7135        {
7136            return None;
7137        }
7138        self.canonical_type_resolution(analyzer, visible_from, unit)
7139            .ok()
7140    }
7141
7142    /// Follow `unit`'s alias chain to the class it names, or report why the
7143    /// chain does not end at one indexed class.
7144    ///
7145    /// A chain that leaves the index - an alias to a template parameter, to a
7146    /// standard-library type, or to any other declaration the workspace does
7147    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
7148    /// there is still nothing to choose between.
7149    fn canonical_type_resolution(
7150        &self,
7151        analyzer: &CppGraphSource<'_>,
7152        visible_from: &ProjectFile,
7153        unit: &CodeUnit,
7154    ) -> Result<CodeUnit, TypeCandidateFailure> {
7155        let mut current = unit.clone();
7156        let mut seen_aliases = HashSet::default();
7157        loop {
7158            let Some(target) = self.structured_alias_target(analyzer, &current) else {
7159                return current
7160                    .is_class()
7161                    .then_some(current)
7162                    .ok_or(TypeCandidateFailure::Unresolvable);
7163            };
7164            if matches!(target, StructuredAliasTarget::Builtin) {
7165                return current
7166                    .is_class()
7167                    .then_some(current)
7168                    .ok_or(TypeCandidateFailure::Unresolvable);
7169            }
7170            if !seen_aliases.insert(current.clone()) {
7171                return Err(TypeCandidateFailure::Unresolvable);
7172            }
7173            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
7174        }
7175    }
7176
7177    pub fn canonical_visible_full_type_unit(
7178        &self,
7179        analyzer: &CppGraphSource<'_>,
7180        visible_from: &ProjectFile,
7181        unit: &CodeUnit,
7182    ) -> Option<CodeUnit> {
7183        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7184        if cpp_class_declaration_strength(analyzer, &canonical)
7185            != CppClassDeclarationStrength::Forward
7186        {
7187            return Some(canonical);
7188        }
7189        let mut full = Vec::new();
7190        for candidate in self
7191            .visible_identifier_candidates(visible_from, canonical.identifier())
7192            .filter(|candidate| {
7193                candidate.is_class()
7194                    && candidate.fq_name() == canonical.fq_name()
7195                    && cpp_class_declaration_strength(analyzer, candidate)
7196                        == CppClassDeclarationStrength::Full
7197            })
7198        {
7199            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7200                full.push(candidate.clone());
7201            }
7202        }
7203        match full.len() {
7204            0 => Some(canonical),
7205            1 => full.pop(),
7206            _ => None,
7207        }
7208    }
7209
7210    fn resolve_structured_alias_target(
7211        &self,
7212        visible_from: &ProjectFile,
7213        declaration: &CodeUnit,
7214        target: &StructuredAliasTarget,
7215    ) -> Option<CodeUnit> {
7216        self.structured_alias_target_resolution(visible_from, declaration, target)
7217            .ok()
7218    }
7219
7220    fn structured_alias_target_resolution(
7221        &self,
7222        visible_from: &ProjectFile,
7223        declaration: &CodeUnit,
7224        target: &StructuredAliasTarget,
7225    ) -> Result<CodeUnit, TypeCandidateFailure> {
7226        let primary =
7227            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7228        let StructuredAliasTarget::Named { arguments, .. } = target else {
7229            return Err(TypeCandidateFailure::Unresolvable);
7230        };
7231        match arguments {
7232            Some(arguments) => self
7233                .resolve_template_arguments(visible_from, primary, arguments)
7234                .map_err(|error| match error {
7235                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7236                        TypeCandidateFailure::Ambiguous
7237                    }
7238                    _ => TypeCandidateFailure::Unresolvable,
7239                }),
7240            None => Ok(primary),
7241        }
7242    }
7243
7244    fn resolve_structured_alias_primary(
7245        &self,
7246        visible_from: &ProjectFile,
7247        declaration: &CodeUnit,
7248        target: &StructuredAliasTarget,
7249    ) -> Option<CodeUnit> {
7250        self.structured_alias_primary_resolution(visible_from, declaration, target)
7251            .ok()
7252    }
7253
7254    fn structured_alias_primary_resolution(
7255        &self,
7256        visible_from: &ProjectFile,
7257        declaration: &CodeUnit,
7258        target: &StructuredAliasTarget,
7259    ) -> Result<CodeUnit, TypeCandidateFailure> {
7260        let StructuredAliasTarget::Named {
7261            components, global, ..
7262        } = target
7263        else {
7264            return Err(TypeCandidateFailure::Unresolvable);
7265        };
7266        let qualified = components.join("::");
7267        let candidates = if *global {
7268            // `::A::B` anchors at the root scope, so a candidate whose
7269            // canonical path merely ends with the spelled components does not
7270            // qualify. Without this filter a global `::Canonical` target also
7271            // collects `alpha::Canonical`, the lookup reports a false
7272            // ambiguity, and the alias arm silently drops out of its
7273            // conflicting family instead of proving the conflict (#2398).
7274            let mut candidates = self.type_candidates(visible_from, &qualified);
7275            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7276            candidates
7277        } else {
7278            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7279        };
7280        logical_type_candidate(candidates)
7281    }
7282
7283    pub fn structured_alias_primary_preserves_target(
7284        &self,
7285        analyzer: &CppGraphSource<'_>,
7286        visible_from: &ProjectFile,
7287        candidate: &CodeUnit,
7288        target: &CodeUnit,
7289    ) -> bool {
7290        let mut current = candidate.clone();
7291        let mut seen = HashSet::default();
7292        let mut matched_target = false;
7293        loop {
7294            if same_visible_symbol(&current, target)
7295                || self.compatible_primary_template_redeclarations(&current, target)
7296            {
7297                matched_target = true;
7298            }
7299            if !seen.insert(current.clone()) {
7300                return false;
7301            }
7302            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7303                return matched_target;
7304            };
7305            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7306                return matched_target;
7307            };
7308            let Some(primary) =
7309                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7310            else {
7311                // A dependent member target such as `Detector<T>::type`
7312                // cannot be reduced to an indexed primary, but a preceding
7313                // structured alias hop may already have proven the requested
7314                // alias identity. Cycles still resolve a primary and are
7315                // rejected by `seen` above.
7316                return matched_target;
7317            };
7318            current = primary;
7319        }
7320    }
7321
7322    pub fn structured_class_alias_resolves_to_target(
7323        &self,
7324        analyzer: &CppGraphSource<'_>,
7325        visible_from: &ProjectFile,
7326        alias: &CodeUnit,
7327        target: &CodeUnit,
7328    ) -> bool {
7329        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7330            return false;
7331        };
7332        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7333            return false;
7334        };
7335        let StructuredAliasTarget::Named {
7336            components, global, ..
7337        } = &alias_target
7338        else {
7339            return false;
7340        };
7341        let lexical_scope = canonical_cpp_scope_components(&owner);
7342        match self.resolve_type_components_lexically_for_target(
7343            analyzer,
7344            visible_from,
7345            components,
7346            *global,
7347            &lexical_scope,
7348            target,
7349        ) {
7350            LexicalTypeResolution::Resolved {
7351                unit, candidates, ..
7352            } => {
7353                same_visible_symbol(&unit, target)
7354                    || self.same_template_member_identity(analyzer, &unit, target)
7355                    || candidates.iter().any(|candidate| {
7356                        same_visible_symbol(candidate, target)
7357                            || self.same_template_member_identity(analyzer, candidate, target)
7358                    })
7359            }
7360            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7361                self.structured_alias_primary_preserves_target(
7362                    analyzer,
7363                    visible_from,
7364                    alias,
7365                    target,
7366                ) || self.flattened_macro_namespace_alias_target_matches(
7367                    analyzer,
7368                    visible_from,
7369                    alias,
7370                    &alias_target,
7371                    target,
7372                )
7373            }
7374        }
7375    }
7376
7377    /// Return true when a class-owned alias names the requested type as one
7378    /// structured qualifier in its target path.
7379    ///
7380    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
7381    /// indexed class. Forward lookup can still retain `Primary` as its bounded
7382    /// canonical identity. Inverse lookup needs the same evidence when later
7383    /// references use only the alias spelling.
7384    pub fn structured_class_alias_path_preserves_target(
7385        &self,
7386        analyzer: &CppGraphSource<'_>,
7387        visible_from: &ProjectFile,
7388        alias: &CodeUnit,
7389        target: &CodeUnit,
7390    ) -> bool {
7391        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7392            return false;
7393        };
7394        let Some(StructuredAliasTarget::Named {
7395            components, global, ..
7396        }) = self.structured_alias_target(analyzer, alias)
7397        else {
7398            return false;
7399        };
7400        let lexical_scope = canonical_cpp_scope_components(&owner);
7401        (1..components.len()).rev().any(|component_count| {
7402            matches!(
7403                self.resolve_type_components_lexically_for_target(
7404                    analyzer,
7405                    visible_from,
7406                    &components[..component_count],
7407                    global,
7408                    &lexical_scope,
7409                    target,
7410                ),
7411                LexicalTypeResolution::Resolved {
7412                    ref unit,
7413                    ref candidates,
7414                    ..
7415                } if same_visible_symbol(unit, target)
7416                    || self.same_template_member_identity(analyzer, unit, target)
7417                    || candidates.iter().any(|candidate| {
7418                        same_visible_symbol(candidate, target)
7419                            || self.same_template_member_identity(analyzer, candidate, target)
7420                    })
7421            )
7422        })
7423    }
7424
7425    fn flattened_macro_namespace_alias_target_matches(
7426        &self,
7427        analyzer: &CppGraphSource<'_>,
7428        visible_from: &ProjectFile,
7429        alias: &CodeUnit,
7430        alias_target: &StructuredAliasTarget,
7431        target: &CodeUnit,
7432    ) -> bool {
7433        let StructuredAliasTarget::Named {
7434            components,
7435            global: false,
7436            arguments: None,
7437        } = alias_target
7438        else {
7439            return false;
7440        };
7441        let Some((target_name, namespace_components)) = components.split_last() else {
7442            return false;
7443        };
7444        if namespace_components.is_empty()
7445            || target_name != target.identifier()
7446            || alias.source() != target.source()
7447            || alias.source() != visible_from
7448            || !target.is_class()
7449            || declared_type_alias(analyzer, target)
7450        {
7451            return false;
7452        }
7453        if self
7454            .resolve_structured_alias_target(visible_from, alias, alias_target)
7455            .is_some()
7456        {
7457            return false;
7458        }
7459
7460        let alias_ranges = analyzer.ranges(alias);
7461        let target_ranges = analyzer.ranges(target);
7462        if alias_ranges.is_empty() || target_ranges.is_empty() {
7463            return false;
7464        }
7465        let alias_start = alias_ranges
7466            .iter()
7467            .map(|range| range.start_byte)
7468            .min()
7469            .expect("non-empty alias ranges have a minimum");
7470        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
7471            return false;
7472        };
7473        let root = prepared.tree().root_node();
7474        let has_matching_declaration = target_ranges
7475            .iter()
7476            .filter(|range| range.end_byte <= alias_start)
7477            .filter_map(|range| node_for_exact_range(root, range))
7478            .any(|node| {
7479                flattened_macro_namespace_components(node, prepared.source())
7480                    .is_some_and(|recovered| recovered == namespace_components)
7481            });
7482        if !has_matching_declaration {
7483            return false;
7484        }
7485
7486        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7487        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7488        guard_requirement_sets_match(&alias_guards, &target_guards)
7489    }
7490
7491    pub fn template_alias_arguments_preserve_target(
7492        &self,
7493        analyzer: &CppGraphSource<'_>,
7494        visible_from: &ProjectFile,
7495        alias: &CodeUnit,
7496        arguments: &[CppTemplateExpression],
7497        target: &CodeUnit,
7498    ) -> bool {
7499        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7500            return false;
7501        };
7502        if metadata.alias_target.is_none()
7503            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7504        {
7505            return false;
7506        }
7507        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7508    }
7509
7510    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7511        self.cpp_template_metadata
7512            .get(unit)
7513            .is_some_and(CppTemplateMetadata::is_primary)
7514    }
7515
7516    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7517        self.cpp_template_metadata
7518            .get(unit)
7519            .is_some_and(CppTemplateMetadata::is_specialization)
7520    }
7521
7522    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7523        same_visible_symbol(left, right)
7524            || self.compatible_primary_template_redeclarations(left, right)
7525    }
7526
7527    pub fn same_template_member_identity(
7528        &self,
7529        analyzer: &CppGraphSource<'_>,
7530        left: &CodeUnit,
7531        right: &CodeUnit,
7532    ) -> bool {
7533        if same_visible_symbol(left, right) {
7534            return true;
7535        }
7536        if left.kind() != right.kind()
7537            || left.identifier() != right.identifier()
7538            || left.signature() != right.signature()
7539        {
7540            return false;
7541        }
7542        let (Some(left_owner), Some(right_owner)) =
7543            (analyzer.parent_of(left), analyzer.parent_of(right))
7544        else {
7545            return false;
7546        };
7547        left_owner.is_class()
7548            && right_owner.is_class()
7549            && self.same_template_owner_identity(&left_owner, &right_owner)
7550    }
7551
7552    fn unique_canonical_type_candidate(
7553        &self,
7554        analyzer: &CppGraphSource<'_>,
7555        visible_from: &ProjectFile,
7556        candidates: &[&CodeUnit],
7557    ) -> Option<CodeUnit> {
7558        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7559            .ok()
7560    }
7561
7562    fn canonical_type_candidate_resolution(
7563        &self,
7564        analyzer: &CppGraphSource<'_>,
7565        visible_from: &ProjectFile,
7566        candidates: &[&CodeUnit],
7567    ) -> Result<CodeUnit, TypeCandidateFailure> {
7568        let mut canonical = Vec::new();
7569        for candidate in candidates {
7570            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7571            if canonical
7572                .iter()
7573                .any(|existing| same_visible_symbol(existing, &resolved))
7574            {
7575                continue;
7576            }
7577            if let Some(existing) = canonical.iter_mut().find(|existing| {
7578                self.compatible_primary_template_redeclarations(existing, &resolved)
7579            }) {
7580                // A forward declaration and its full primary-template
7581                // definition are one C++ type even when they live in
7582                // different headers and alpha-rename their parameters. The
7583                // target-preserving path already reconciles this family; do
7584                // the same for ordinary canonical lookup so an out-of-line
7585                // member's lexical owner is not made ambiguous by its own
7586                // forward declaration. Retain the strongest physical
7587                // declaration for later owner/range queries.
7588                if matches!(
7589                    (
7590                        cpp_class_declaration_strength(analyzer, existing),
7591                        cpp_class_declaration_strength(analyzer, &resolved),
7592                    ),
7593                    (
7594                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7595                        CppClassDeclarationStrength::Full,
7596                    ) | (
7597                        CppClassDeclarationStrength::Unknown,
7598                        CppClassDeclarationStrength::Forward,
7599                    )
7600                ) {
7601                    *existing = resolved;
7602                }
7603                continue;
7604            }
7605            canonical.push(resolved);
7606            if canonical.len() > 1 {
7607                return Err(TypeCandidateFailure::Ambiguous);
7608            }
7609        }
7610        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7611    }
7612
7613    pub fn unique_type_candidate_preserving_target(
7614        &self,
7615        analyzer: &CppGraphSource<'_>,
7616        visible_from: &ProjectFile,
7617        candidates: &[&CodeUnit],
7618        target: &CodeUnit,
7619    ) -> Option<CodeUnit> {
7620        // C++ headers often expose one logical type through mutually exclusive
7621        // physical declarations, for example a class in the fallback branch
7622        // and a `using` alias to the standard-library type in the configured
7623        // branch. The index intentionally retains both declarations so forward
7624        // lookup can report each target. Preserve the requested target when
7625        // that is the only ambiguity: every candidate has the same type kind,
7626        // exact canonical FQN, and source file, and the requested declaration
7627        // itself is one of the physical candidates. Do not merge same-named
7628        // declarations from different files or namespaces; those remain
7629        // ambiguous and fail closed below.
7630        if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7631            || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7632        {
7633            return Some(target.clone());
7634        }
7635        let mut resolved_candidates = Vec::new();
7636        for candidate in candidates {
7637            // An ifdef branch that aliases an unindexed system type (for
7638            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
7639            // canonicalized. That branch does not name `target`. Dropping it
7640            // keeps the branch that does. Failing the whole family here would
7641            // deny every usage of the reachable spelling (#2368).
7642            let Some(resolved) =
7643                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7644            else {
7645                continue;
7646            };
7647            if resolved_candidates
7648                .iter()
7649                .any(|existing| same_visible_symbol(existing, &resolved))
7650            {
7651                continue;
7652            }
7653            resolved_candidates.push(resolved);
7654        }
7655        match resolved_candidates.as_slice() {
7656            [] => None,
7657            [single] => Some(single.clone()),
7658            // The branches disagree about what the name aliases. When they are
7659            // spellings of one entity (#1845) that disagreement is a build
7660            // configuration, not a choice between types, so it must not deny
7661            // the requested target its reference.
7662            _ => self
7663                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7664                .map(|_| target.clone()),
7665        }
7666    }
7667
7668    /// The declaration a same-file same-FQN family stands for when a reference
7669    /// names `target`, or `None` when the candidates are not one family or the
7670    /// family does not name `target`.
7671    ///
7672    /// A translation unit cannot hold two different types under one qualified
7673    /// name, so several same-kind declarations of one FQN in one file are
7674    /// alternate spellings of one entity - the configuration branches of an
7675    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
7676    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
7677    /// targets differ; canonicalizing each branch on its own and then demanding
7678    /// agreement reports an ambiguity that denies every declaration in the
7679    /// family its usages (#1845). The family names `target` when it declares
7680    /// it, or when one branch's alias chain reaches it.
7681    ///
7682    /// Declarations in different files or namespaces are distinct entities and
7683    /// are deliberately excluded: their disagreement is a real ambiguity.
7684    pub fn same_fqn_type_spelling_for_target<'b>(
7685        &self,
7686        analyzer: &CppGraphSource<'_>,
7687        visible_from: &ProjectFile,
7688        candidates: &[&'b CodeUnit],
7689        target: &CodeUnit,
7690    ) -> Option<&'b CodeUnit> {
7691        let [first, rest @ ..] = candidates else {
7692            return None;
7693        };
7694        if rest.is_empty()
7695            || !rest.iter().all(|candidate| {
7696                candidate.kind() == first.kind()
7697                    && candidate.fq_name() == first.fq_name()
7698                    && candidate.source() == first.source()
7699            })
7700        {
7701            return None;
7702        }
7703        candidates
7704            .iter()
7705            .copied()
7706            .find(|candidate| same_symbol(candidate, target))
7707            .or_else(|| {
7708                candidates.iter().copied().find(|candidate| {
7709                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7710                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7711                })
7712            })
7713    }
7714
7715    pub fn alternate_same_fqn_type_declarations(
7716        &self,
7717        analyzer: &CppGraphSource<'_>,
7718        candidates: &[&CodeUnit],
7719        target: &CodeUnit,
7720    ) -> bool {
7721        let Some(first) = candidates.first() else {
7722            return false;
7723        };
7724        let same_api = first.kind() == target.kind()
7725            && first.fq_name() == target.fq_name()
7726            && first.source() == target.source()
7727            && candidates.iter().all(|candidate| {
7728                candidate.kind() == target.kind()
7729                    && candidate.fq_name() == target.fq_name()
7730                    && candidate.source() == target.source()
7731            })
7732            && candidates
7733                .iter()
7734                .any(|candidate| same_symbol(candidate, target))
7735            && candidates
7736                .iter()
7737                .any(|candidate| !same_logical_symbol(candidate, target));
7738        if !same_api {
7739            return false;
7740        }
7741
7742        let requirements = candidates
7743            .iter()
7744            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7745            .collect::<Vec<_>>();
7746        requirements.len() > 1
7747            && requirements
7748                .iter()
7749                .all(|requirement| !requirement.is_empty())
7750            && requirements.iter().enumerate().all(|(index, left)| {
7751                requirements[index + 1..].iter().all(|right| {
7752                    left.iter().all(|(_, left_guards)| {
7753                        right.iter().all(|(_, right_guards)| {
7754                            merge_preprocessor_guards(left_guards, right_guards).is_none()
7755                        })
7756                    })
7757                })
7758            })
7759    }
7760
7761    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7762        let mut pending = vec![terms.to_vec()];
7763        while let Some(branch_terms) = pending.pop() {
7764            let mut normalized = Vec::new();
7765            let mut covers_branch = false;
7766            for term in branch_terms {
7767                if term.iter().any(|guard| term.contains(&guard.negated())) {
7768                    continue;
7769                }
7770                if term.is_empty() {
7771                    covers_branch = true;
7772                    break;
7773                }
7774                if !normalized.iter().any(|existing| existing == &term) {
7775                    normalized.push(term);
7776                }
7777            }
7778            if covers_branch {
7779                continue;
7780            }
7781            let Some(split_guard) = normalized
7782                .iter()
7783                .flat_map(|term| term.iter())
7784                .next()
7785                .cloned()
7786            else {
7787                return false;
7788            };
7789            let negated_guard = split_guard.negated();
7790            let mut when_defined = Vec::new();
7791            let mut when_undefined = Vec::new();
7792            for term in normalized {
7793                if term.contains(&negated_guard) {
7794                    // This term cannot hold when `split_guard` is true.
7795                } else if term.contains(&split_guard) {
7796                    let mut reduced = term.clone();
7797                    reduced.remove(&split_guard);
7798                    when_defined.push(reduced);
7799                } else {
7800                    when_defined.push(term.clone());
7801                }
7802                if term.contains(&split_guard) {
7803                    // This term cannot hold when `split_guard` is false.
7804                } else if term.contains(&negated_guard) {
7805                    let mut reduced = term;
7806                    reduced.remove(&negated_guard);
7807                    when_undefined.push(reduced);
7808                } else {
7809                    when_undefined.push(term);
7810                }
7811            }
7812            pending.push(when_defined);
7813            pending.push(when_undefined);
7814        }
7815        true
7816    }
7817
7818    /// The byte range of the one `#if` family with a terminal `#else` that holds
7819    /// every physical declaration of every candidate, or `None` when they do not
7820    /// share one such family.
7821    ///
7822    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
7823    /// whose macros changed between declarations. Require every physical range to
7824    /// belong to one syntax-tree family with a terminal `#else` before the terms
7825    /// can prove branch coverage.
7826    fn declarations_share_exhaustive_conditional_family(
7827        &self,
7828        analyzer: &CppGraphSource<'_>,
7829        candidates: &[&CodeUnit],
7830    ) -> Option<(usize, usize)> {
7831        let mut family_range = None;
7832        for candidate in candidates {
7833            let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
7834            let root = prepared.tree().root_node();
7835            let mut candidate_family = None;
7836            for range in analyzer.ranges(candidate) {
7837                let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
7838                let family = preprocessor_conditional_family_for_declaration(node)?;
7839                let key = (family.start_byte(), family.end_byte());
7840                if candidate_family.is_some_and(|existing| existing != key) {
7841                    return None;
7842                }
7843                candidate_family = Some(key);
7844            }
7845            let candidate_family = candidate_family?;
7846            if family_range.is_some_and(|existing| existing != candidate_family) {
7847                return None;
7848            }
7849            family_range = Some(candidate_family);
7850        }
7851        family_range
7852    }
7853
7854    pub fn complementary_same_fqn_type_declarations(
7855        &self,
7856        analyzer: &CppGraphSource<'_>,
7857        candidates: &[&CodeUnit],
7858        target: &CodeUnit,
7859    ) -> bool {
7860        if candidates.len() < 2
7861            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7862            || self
7863                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7864                .is_none()
7865        {
7866            return false;
7867        }
7868        Self::preprocessor_guard_terms_cover_all_paths(
7869            &self.declaration_family_guard_terms(analyzer, candidates),
7870        )
7871    }
7872
7873    fn declaration_family_guard_terms(
7874        &self,
7875        analyzer: &CppGraphSource<'_>,
7876        candidates: &[&CodeUnit],
7877    ) -> Vec<HashSet<PreprocessorGuard>> {
7878        candidates
7879            .iter()
7880            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7881            .map(|(_, guards)| guards)
7882            .collect()
7883    }
7884
7885    /// A callable name declared on every branch of one completed `#if`/`#else`
7886    /// family is declared on every configuration path, so a reference below the
7887    /// whole family sees one of the branches whatever the preprocessor decides.
7888    /// Answer the family's end byte: only past `#endif` is every branch's
7889    /// declaration behind the reference.
7890    ///
7891    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
7892    /// and shares both of its primitives. It does not require two distinct
7893    /// `CodeUnit`s: branches that declare the same signature can collapse into
7894    /// one unit carrying one physical range per branch.
7895    ///
7896    /// The branches are alternate spellings of one declaration, never competing
7897    /// declarations, so only the first branch stands for the family. Reporting
7898    /// every branch as visible would turn a name the source declares exactly
7899    /// once into an ambiguity between build configurations.
7900    fn exhaustive_guard_family_activation(
7901        &self,
7902        analyzer: &CppGraphSource<'_>,
7903        prepared: &PreparedSyntaxTree,
7904        candidate: &CodeUnit,
7905        reference: &CallableReferenceContext<'_>,
7906    ) -> Option<usize> {
7907        // Branch coverage says nothing about scope: a block-local declaration
7908        // stays invisible however many branches declare it.
7909        if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
7910            return None;
7911        }
7912        let family = self
7913            .visible_identifier_candidates(candidate.source(), candidate.identifier())
7914            .filter(|peer| {
7915                peer.kind() == candidate.kind()
7916                    && peer.fq_name() == candidate.fq_name()
7917                    && peer.source() == candidate.source()
7918            })
7919            .collect::<Vec<_>>();
7920        let (_, family_end) =
7921            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7922        if !Self::preprocessor_guard_terms_cover_all_paths(
7923            &self.declaration_family_guard_terms(analyzer, &family),
7924        ) {
7925            return None;
7926        }
7927        // A reference whose own guards pick one branch already reaches that
7928        // branch through the ordinary same-guard path; the family must not
7929        // resurrect the branch the reference contradicts.
7930        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7931            .iter()
7932            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7933        {
7934            return None;
7935        }
7936        (first_declaration_byte(analyzer, candidate)?
7937            == family
7938                .iter()
7939                .filter_map(|peer| first_declaration_byte(analyzer, peer))
7940                .min()?)
7941        .then_some(family_end)
7942    }
7943
7944    fn type_candidate_preserving_target(
7945        &self,
7946        analyzer: &CppGraphSource<'_>,
7947        visible_from: &ProjectFile,
7948        candidate: &CodeUnit,
7949        target: &CodeUnit,
7950    ) -> Option<CodeUnit> {
7951        let mut current = candidate.clone();
7952        let mut matched_target = same_visible_symbol(&current, target)
7953            || self.compatible_primary_template_redeclarations(&current, target);
7954        let mut seen = HashSet::default();
7955        loop {
7956            if !seen.insert(current.clone()) {
7957                return None;
7958            }
7959            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7960                return matched_target
7961                    .then(|| target.clone())
7962                    .or_else(|| current.is_class().then_some(current));
7963            };
7964            if self.flattened_macro_namespace_alias_target_matches(
7965                analyzer,
7966                visible_from,
7967                &current,
7968                &alias_target,
7969                target,
7970            ) {
7971                return Some(target.clone());
7972            }
7973            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7974                return matched_target
7975                    .then(|| target.clone())
7976                    .or_else(|| current.is_class().then_some(current));
7977            }
7978            // A non-template alias can name a template alias with explicit
7979            // arguments (for example, `using Result = Expected<int>`).  When
7980            // the requested target is that alias's primary declaration, keep
7981            // the primary identity before expanding the RHS arguments.  The
7982            // expansion would otherwise canonicalize through the underlying
7983            // implementation type and lose the target spelling used by the
7984            // forward resolver.
7985            if !self.cpp_template_metadata.contains_key(&current)
7986                && let Some(primary) =
7987                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7988                && (same_visible_symbol(&primary, target)
7989                    || self.compatible_primary_template_redeclarations(&primary, target))
7990            {
7991                return Some(target.clone());
7992            }
7993            if same_visible_symbol(&current, target) {
7994                return Some(target.clone());
7995            }
7996            if self.cpp_template_metadata.contains_key(&current) {
7997                return None;
7998            }
7999            let Some(next) =
8000                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
8001            else {
8002                return matched_target.then(|| target.clone());
8003            };
8004            current = next;
8005            matched_target |= same_visible_symbol(&current, target)
8006                || self.compatible_primary_template_redeclarations(&current, target);
8007        }
8008    }
8009
8010    fn compatible_primary_template_redeclarations(
8011        &self,
8012        left: &CodeUnit,
8013        right: &CodeUnit,
8014    ) -> bool {
8015        let (Some(left_metadata), Some(right_metadata)) = (
8016            self.cpp_template_metadata.get(left),
8017            self.cpp_template_metadata.get(right),
8018        ) else {
8019            return false;
8020        };
8021        left_metadata.primary_fq_name == right_metadata.primary_fq_name
8022            && left_metadata.is_primary()
8023            && right_metadata.is_primary()
8024            && cpp_reconcile_primary_template_parameters(
8025                &[(left, left_metadata), (right, right_metadata)],
8026                right,
8027            )
8028            .is_some()
8029    }
8030
8031    fn alias_candidate_may_preserve_target(
8032        &self,
8033        analyzer: &CppGraphSource<'_>,
8034        visible_from: &ProjectFile,
8035        candidate: &CodeUnit,
8036        target: &CodeUnit,
8037    ) -> bool {
8038        let mut current = candidate.clone();
8039        let mut seen = HashSet::default();
8040        loop {
8041            if same_visible_symbol(&current, target)
8042                || self.compatible_primary_template_redeclarations(&current, target)
8043            {
8044                return true;
8045            }
8046            if self.cpp_template_metadata.contains_key(&current) {
8047                return true;
8048            }
8049            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8050                return false;
8051            };
8052            let StructuredAliasTarget::Named {
8053                components,
8054                global,
8055                arguments,
8056            } = alias_target
8057            else {
8058                return false;
8059            };
8060            if arguments.is_some() || !seen.insert(current.clone()) {
8061                return true;
8062            }
8063            let qualified = components.join("::");
8064            let next = if global {
8065                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8066            } else {
8067                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
8068            };
8069            let Some(next) = next else {
8070                return true;
8071            };
8072            current = next;
8073        }
8074    }
8075
8076    fn alias_candidate_structurally_reaches_target(
8077        &self,
8078        analyzer: &CppGraphSource<'_>,
8079        visible_from: &ProjectFile,
8080        candidate: &CodeUnit,
8081        target: &CodeUnit,
8082    ) -> bool {
8083        let mut current = candidate.clone();
8084        let mut seen = HashSet::default();
8085        loop {
8086            if same_visible_symbol(&current, target)
8087                || self.compatible_primary_template_redeclarations(&current, target)
8088                || self
8089                    .cpp_template_metadata
8090                    .get(&current)
8091                    .zip(self.cpp_template_metadata.get(target))
8092                    .is_some_and(|(current, target)| {
8093                        current.primary_fq_name == target.primary_fq_name
8094                    })
8095            {
8096                return true;
8097            }
8098            if !seen.insert(current.clone()) {
8099                return false;
8100            }
8101            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8102                return false;
8103            };
8104            if let StructuredAliasTarget::Named { components, .. } = &alias_target
8105                && components[..components.len().saturating_sub(1)]
8106                    .iter()
8107                    .any(|component| {
8108                        self.type_reference_component_directly_names_target(component, target)
8109                    })
8110                && self.structured_class_alias_path_preserves_target(
8111                    analyzer,
8112                    visible_from,
8113                    &current,
8114                    target,
8115                )
8116            {
8117                return true;
8118            }
8119            if matches!(alias_target, StructuredAliasTarget::Builtin) {
8120                return false;
8121            }
8122            let Some(primary) =
8123                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
8124            else {
8125                return false;
8126            };
8127            current = primary;
8128        }
8129    }
8130
8131    /// Whether one indexed alias component in a qualified type reference has
8132    /// a structured path to `target`.
8133    ///
8134    /// Alias terminal names such as `type` are deliberately common. A
8135    /// file-wide name set cannot distinguish `Gen<T>::type`, whose indexed RHS
8136    /// names the target, from thousands of unrelated `Traits<T>::type`
8137    /// references. Match every qualified prefix against the alias's canonical
8138    /// scope before following its structured RHS. An unresolved dependent RHS
8139    /// fails this coarse gate only when no other component names the target;
8140    /// references such as `Identity<Target>::type` are admitted by that target
8141    /// component before this helper is called.
8142    pub fn qualified_alias_reference_may_reach_target(
8143        &self,
8144        analyzer: &CppGraphSource<'_>,
8145        file: &ProjectFile,
8146        components: &[String],
8147        global: bool,
8148        target: &CodeUnit,
8149    ) -> bool {
8150        for end in 0..components.len() {
8151            let spelled = &components[..=end];
8152            for candidate in self
8153                .visible_identifier_candidates(file, &components[end])
8154                .filter(|candidate| declared_type_alias(analyzer, candidate))
8155            {
8156                let candidate_components = canonical_cpp_scope_components(candidate);
8157                let shape_matches = if global {
8158                    candidate_components == spelled
8159                } else {
8160                    candidate_components.ends_with(spelled)
8161                };
8162                if shape_matches
8163                    && self.alias_candidate_structurally_reaches_target(
8164                        analyzer, file, candidate, target,
8165                    )
8166                {
8167                    return true;
8168                }
8169            }
8170        }
8171        false
8172    }
8173
8174    /// Every indexed type declaration `raw_name` names when it is written in
8175    /// `declaration`'s namespace: the innermost enclosing namespace that holds
8176    /// the name wins, otherwise the name is looked up unqualified.
8177    fn type_candidates_for_declaration<'b>(
8178        &'b self,
8179        visible_from: &ProjectFile,
8180        declaration: &CodeUnit,
8181        raw_name: &str,
8182    ) -> Vec<&'b CodeUnit> {
8183        let Some(normalized) = normalize_reference_name(raw_name) else {
8184            return Vec::new();
8185        };
8186        if let Some(namespace) = cpp_namespace_for(declaration) {
8187            for prefix in namespace_prefixes(&namespace) {
8188                let qualified = format!("{prefix}::{normalized}");
8189                let candidates = self.type_candidates(visible_from, &qualified);
8190                if !candidates.is_empty() {
8191                    return candidates;
8192                }
8193            }
8194        }
8195        self.type_candidates(visible_from, &normalized)
8196    }
8197
8198    fn resolve_unique_type_for_declaration(
8199        &self,
8200        visible_from: &ProjectFile,
8201        declaration: &CodeUnit,
8202        raw_name: &str,
8203    ) -> Option<CodeUnit> {
8204        unique_logical_type_candidate(self.type_candidates_for_declaration(
8205            visible_from,
8206            declaration,
8207            raw_name,
8208        ))
8209    }
8210
8211    /// The type `raw_name` names at `declaration`, answered with the
8212    /// declaration that defines it.
8213    ///
8214    /// C spells one type twice. `typedef struct T T;` in the header that
8215    /// publishes the handle indexes an incomplete `struct T` beside the
8216    /// complete `struct T { ... }` in the header that declares the members, and
8217    /// both carry the same kind and fully qualified name, so
8218    /// [`logical_type_candidate`] is free to answer with either. mbedtls writes
8219    /// exactly that: `include/mbedtls/ssl.h` forward-declares
8220    /// `mbedtls_ssl_handshake_params` for the pointer member
8221    /// `mbedtls_ssl_handshake_params *MBEDTLS_PRIVATE(handshake)` while
8222    /// `library/ssl_misc.h` defines its fields. A receiver typed from a field
8223    /// declaration is asked for its members on the next link of the chain, and
8224    /// only the definition owns them (#2982).
8225    ///
8226    /// Competing definitions leave nothing to choose between, so keep the
8227    /// logical answer rather than picking one of them.
8228    fn resolve_defining_type_for_declaration(
8229        &self,
8230        analyzer: &CppGraphSource<'_>,
8231        visible_from: &ProjectFile,
8232        declaration: &CodeUnit,
8233        raw_name: &str,
8234    ) -> Option<CodeUnit> {
8235        let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8236        let logical = unique_logical_type_candidate(candidates.clone())?;
8237        let mut defining = candidates.into_iter().filter(|candidate| {
8238            candidate.is_class()
8239                && cpp_class_declaration_strength(analyzer, candidate)
8240                    == CppClassDeclarationStrength::Full
8241        });
8242        match (defining.next(), defining.next()) {
8243            (Some(unique_definition), None) => Some(unique_definition.clone()),
8244            _ => Some(logical),
8245        }
8246    }
8247
8248    pub fn resolves_to_type(
8249        &self,
8250        analyzer: &CppGraphSource<'_>,
8251        file: &ProjectFile,
8252        raw_name: &str,
8253        target: &CodeUnit,
8254    ) -> bool {
8255        let Some(normalized) = normalize_reference_name(raw_name) else {
8256            return false;
8257        };
8258        let candidates = self.type_candidates(file, &normalized);
8259        if candidates.is_empty() {
8260            return self.parser_alias_resolves_to_type(file, raw_name, target);
8261        }
8262        let Some(resolved) =
8263            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8264        else {
8265            return false;
8266        };
8267        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8268    }
8269
8270    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8271        let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
8272        let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
8273        match resolved.kind() {
8274            CodeUnitType::Class => Some(resolved),
8275            _ if is_type_alias(&resolved) => self.alias_target(&resolved),
8276            _ => None,
8277        }
8278    }
8279
8280    /// Whether two callable declarations declare one function.
8281    ///
8282    /// [`same_logical_symbol`] compares the persisted signature strings, which
8283    /// embed each parameter type exactly as it was spelled. A header
8284    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
8285    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
8286    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
8287    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
8288    /// This resolves the written parameter names before comparing them and
8289    /// reports the same answer the language does for the cases it can prove.
8290    ///
8291    /// Everything it cannot prove stays two symbols: a template declaration, a
8292    /// parameter with no comparable shape, a name that resolves on one side
8293    /// only, and an alias chain it cannot follow safely (#2010).
8294    pub fn same_logical_callable(
8295        &self,
8296        analyzer: &CppGraphSource<'_>,
8297        left: &CodeUnit,
8298        right: &CodeUnit,
8299    ) -> bool {
8300        if same_logical_symbol(left, right) {
8301            return true;
8302        }
8303        if left.kind() != right.kind()
8304            || !left.is_callable()
8305            || !right.is_callable()
8306            || left.fq_name() != right.fq_name()
8307        {
8308            return false;
8309        }
8310        // A template declaration and its out-of-line body can also diverge
8311        // outside the parameter list - `template <class T>` against
8312        // `template <typename T>` - and the template head is part of the
8313        // persisted signature. Deciding template-head equivalence is a
8314        // separate question, so templates keep string identity.
8315        if self.callable_is_template_declaration(analyzer, left)
8316            || self.callable_is_template_declaration(analyzer, right)
8317        {
8318            return false;
8319        }
8320        let (Some(left_comparable), Some(right_comparable)) = (
8321            self.callable_comparable(analyzer, left),
8322            self.callable_comparable(analyzer, right),
8323        ) else {
8324            return false;
8325        };
8326        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
8327        // return type and requires-clause are part of C++ callable identity and
8328        // an out-of-line definition repeats them verbatim, so they must agree
8329        // as written.
8330        if left_comparable.suffix != right_comparable.suffix
8331            || left_comparable.shapes.len() != right_comparable.shapes.len()
8332        {
8333            return false;
8334        }
8335        left_comparable
8336            .shapes
8337            .iter()
8338            .zip(right_comparable.shapes.iter())
8339            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8340                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8341                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8342                    self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8343                }
8344                // An unstructured parameter records that the reduction failed,
8345                // not that the two spellings mean the same type, so it agrees
8346                // with nothing - including another unstructured parameter.
8347                _ => false,
8348            })
8349    }
8350
8351    /// Compare two parameter shapes node by node with an explicit paired stack.
8352    ///
8353    /// Shape variants and cv-qualifiers must agree exactly at every level; only
8354    /// the named leaves may be spelled differently, and they agree when they
8355    /// resolve to one type declaration.
8356    fn comparable_shapes_agree(
8357        &self,
8358        analyzer: &CppGraphSource<'_>,
8359        left: &CppComparableParameter,
8360        right: &CppComparableParameter,
8361    ) -> bool {
8362        let mut stack = vec![(left.root(), right.root())];
8363        while let Some((left_index, right_index)) = stack.pop() {
8364            match (left.node(left_index), right.node(right_index)) {
8365                (
8366                    CppComparableNode::Named {
8367                        name: left_name,
8368                        primitive: left_primitive,
8369                        konst: left_konst,
8370                        volatil: left_volatil,
8371                    },
8372                    CppComparableNode::Named {
8373                        name: right_name,
8374                        primitive: right_primitive,
8375                        konst: right_konst,
8376                        volatil: right_volatil,
8377                    },
8378                ) => {
8379                    if left_konst != right_konst
8380                        || left_volatil != right_volatil
8381                        || left_primitive != right_primitive
8382                        || !self.comparable_names_agree(
8383                            analyzer,
8384                            left_name,
8385                            right_name,
8386                            *left_primitive,
8387                        )
8388                    {
8389                        return false;
8390                    }
8391                }
8392                (
8393                    CppComparableNode::Pointer {
8394                        inner: left_inner,
8395                        konst: left_konst,
8396                        volatil: left_volatil,
8397                    },
8398                    CppComparableNode::Pointer {
8399                        inner: right_inner,
8400                        konst: right_konst,
8401                        volatil: right_volatil,
8402                    },
8403                ) => {
8404                    if left_konst != right_konst || left_volatil != right_volatil {
8405                        return false;
8406                    }
8407                    stack.push((*left_inner, *right_inner));
8408                }
8409                (
8410                    CppComparableNode::Reference { inner: left_inner },
8411                    CppComparableNode::Reference { inner: right_inner },
8412                )
8413                | (
8414                    CppComparableNode::Array { inner: left_inner },
8415                    CppComparableNode::Array { inner: right_inner },
8416                ) => stack.push((*left_inner, *right_inner)),
8417                (
8418                    CppComparableNode::Generic {
8419                        base: left_base,
8420                        arguments: left_arguments,
8421                    },
8422                    CppComparableNode::Generic {
8423                        base: right_base,
8424                        arguments: right_arguments,
8425                    },
8426                ) => {
8427                    if left_arguments.len() != right_arguments.len() {
8428                        return false;
8429                    }
8430                    stack.push((*left_base, *right_base));
8431                    stack.extend(
8432                        left_arguments.iter().zip(right_arguments.iter()).map(
8433                            |(left_argument, right_argument)| (*left_argument, *right_argument),
8434                        ),
8435                    );
8436                }
8437                _ => return false,
8438            }
8439        }
8440        true
8441    }
8442
8443    /// Whether two written type names denote one type.
8444    ///
8445    /// A primitive denotes the same type in every scope, so its recorded
8446    /// lexical scope is noise and its spelling decides. A nominal name is
8447    /// resolved on each side independently: two resolved names agree when they
8448    /// reach one type declaration, and two unresolved names agree only on
8449    /// exact agreement of what was written, which is no weaker than the
8450    /// whole-signature string equality this comparison replaces. Resolution on
8451    /// one side only is evidence of difference, never of agreement.
8452    fn comparable_names_agree(
8453        &self,
8454        analyzer: &CppGraphSource<'_>,
8455        left: &StructuredTypeName,
8456        right: &StructuredTypeName,
8457        primitive: bool,
8458    ) -> bool {
8459        if primitive {
8460            return left.path() == right.path();
8461        }
8462        match (
8463            self.comparable_name_terminal(analyzer, left),
8464            self.comparable_name_terminal(analyzer, right),
8465        ) {
8466            (Some(left_terminal), Some(right_terminal)) => {
8467                same_logical_symbol(&left_terminal, &right_terminal)
8468            }
8469            (None, None) => {
8470                left.path() == right.path() && left.is_absolute() == right.is_absolute()
8471            }
8472            _ => false,
8473        }
8474    }
8475
8476    /// The class declaration a written type name denotes, or `None` when the
8477    /// workspace cannot prove one.
8478    ///
8479    /// The lookup is a closure-independent lexical-scope prefix walk over the
8480    /// stored C++ identifier index rather than a visibility lookup: a body's
8481    /// `.cpp` is almost never in the reference file's include closure. Any name
8482    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
8483    /// cannot invent a type the compiler could not see; `using`-directives are
8484    /// not modelled, and a name that needs one stays unresolved.
8485    fn comparable_name_terminal(
8486        &self,
8487        analyzer: &CppGraphSource<'_>,
8488        name: &StructuredTypeName,
8489    ) -> Option<CodeUnit> {
8490        let mut current = self.comparable_name_declaration(analyzer, name)?;
8491        let mut visited = HashSet::default();
8492        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8493            // The alias question is asked before the class question, and
8494            // through `declared_type_alias` rather than `is_type_alias`,
8495            // because extraction records `using A8 = A7;` as a *Class* unit
8496            // whose signature is the alias declaration. Reading the kind first
8497            // would end the chase on the alias itself and report an alias
8498            // spelling and its underlying class as two types (#2010).
8499            if !declared_type_alias(analyzer, &current) {
8500                return current.is_class().then_some(current);
8501            }
8502            if !visited.insert(current.clone()) {
8503                return None;
8504            }
8505            let signature = current.signature()?;
8506            // `cpp_alias_declaration_target_text` reads the declaration's
8507            // `type` field only, so `typedef Foo *Bar` reports `Foo` and the
8508            // pointer is silently dropped. Substituting such an alias would
8509            // fuse `f(Bar)` and `f(Foo)`, which are two functions.
8510            if cpp_alias_declaration_adds_indirection(signature) {
8511                return None;
8512            }
8513            let raw_target = cpp_alias_declaration_target_text(signature)?;
8514            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
8515        }
8516        None
8517    }
8518
8519    /// The declaration one alias hop lands on: the type `raw_target` names,
8520    /// looked up from the alias declaration's own enclosing namespace.
8521    ///
8522    /// The hop takes the same closure-independent prefix walk the first lookup
8523    /// took, and deliberately not `resolve_type_for_declaration`: that one
8524    /// answers out of the `VisibilityIndex`, which is rooted at the reference
8525    /// file, while the alias declaration this hop starts from is reached
8526    /// through the workspace definition index and its file need not be in that
8527    /// root's include closure - where the visibility lookup answers nothing and
8528    /// the chase would stop on the alias itself (#2010).
8529    fn comparable_alias_target(
8530        &self,
8531        analyzer: &CppGraphSource<'_>,
8532        alias: &CodeUnit,
8533        raw_target: &str,
8534    ) -> Option<CodeUnit> {
8535        // `raw_target` is the alias declaration's written type text, so it is a
8536        // plain `::`-joined qualified-id: the same domain the shared symbol-path
8537        // parser reads, and the same leading `::` that marks an absolute name
8538        // everywhere else this crate normalizes a reference.
8539        let absolute = raw_target.trim_start().starts_with("::");
8540        let normalized = normalize_reference_name(raw_target)?;
8541        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8542            brokk_bifrost_core::analyzer::Language::Cpp,
8543            &normalized,
8544        );
8545        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8546            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8547                brokk_bifrost_core::analyzer::Language::Cpp,
8548                &namespace,
8549            )
8550        });
8551        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8552        self.comparable_name_declaration(analyzer, &name)
8553    }
8554
8555    /// The one type declaration `name` names, by enclosing scope, innermost
8556    /// first.
8557    ///
8558    /// The first prefix depth that names anything decides: an inner scope hides
8559    /// an outer one, so a match there is the answer even when an outer scope
8560    /// also declares the name. Several logically distinct declarations at that
8561    /// depth are an ambiguity this comparison must not guess at.
8562    fn comparable_name_declaration(
8563        &self,
8564        analyzer: &CppGraphSource<'_>,
8565        name: &StructuredTypeName,
8566    ) -> Option<CodeUnit> {
8567        // The same parameter-name pair is commonly compared once for every
8568        // physical declaration in a candidate set. Keep the index lookup under
8569        // the cache lock so concurrent workers cannot all miss the same key
8570        // and repeat the workspace candidate filter. `None` is a meaningful
8571        // result too: unresolved and ambiguous names must remain unresolved on
8572        // every later comparison.
8573        let mut cache = self
8574            .comparable_name_declarations
8575            .lock()
8576            .expect("C++ comparable name declaration cache poisoned");
8577        if let Some(cached) = cache.get(name) {
8578            return cached.clone();
8579        }
8580        let interner = segment_interner();
8581        let identifier = name.path().last()?;
8582        let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8583        let first_depth = if name.is_absolute() {
8584            0
8585        } else {
8586            name.lexical_scope().len()
8587        };
8588        let mut resolved = None;
8589        for depth in (0..=first_depth).rev() {
8590            let mut structured = FqName::new();
8591            for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8592                structured.push(interner.intern(component, SegmentKind::Unknown));
8593            }
8594            let mut candidates = candidates_by_identifier
8595                .iter()
8596                .filter(|unit| unit.fq().same_segment_texts(&structured))
8597                .filter(|unit| {
8598                    unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8599                })
8600                .cloned();
8601            let Some(first) = candidates.next() else {
8602                continue;
8603            };
8604            resolved = candidates
8605                .all(|unit| same_logical_symbol(&unit, &first))
8606                .then_some(first);
8607            break;
8608        }
8609        cache.insert(name.clone(), resolved.clone());
8610        resolved
8611    }
8612
8613    /// The comparison inputs of one callable declaration, extracted once.
8614    ///
8615    /// The comparison itself runs only when two candidates share kind and fully
8616    /// qualified name but not signature, which is rare; re-reading the same
8617    /// declaration for every pair in a candidate set is not.
8618    fn callable_comparable(
8619        &self,
8620        analyzer: &CppGraphSource<'_>,
8621        unit: &CodeUnit,
8622    ) -> Option<Arc<ExtractedComparable>> {
8623        if let Some(cached) = self
8624            .callable_comparables
8625            .lock()
8626            .expect("C++ callable comparable cache poisoned")
8627            .get(unit)
8628            .cloned()
8629        {
8630            return cached;
8631        }
8632        let extracted = self
8633            .extract_callable_comparable(analyzer, unit)
8634            .map(Arc::new);
8635        self.callable_comparables
8636            .lock()
8637            .expect("C++ callable comparable cache poisoned")
8638            .insert(unit.clone(), extracted.clone());
8639        extracted
8640    }
8641
8642    fn extract_callable_comparable(
8643        &self,
8644        analyzer: &CppGraphSource<'_>,
8645        unit: &CodeUnit,
8646    ) -> Option<ExtractedComparable> {
8647        let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
8648        let root = prepared.tree().root_node();
8649        let declarator = analyzer
8650            .ranges(unit)
8651            .into_iter()
8652            .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
8653        Some(ExtractedComparable {
8654            // One question about one declarator: indexing the file's tree would
8655            // cost more than the walk it saves.
8656            shapes: cpp_comparable_parameter_shapes(
8657                declarator,
8658                prepared.source(),
8659                &ParentIndex::unindexed(),
8660            ),
8661            suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
8662        })
8663    }
8664
8665    pub fn canonical_type_for_reference(
8666        &self,
8667        file: &ProjectFile,
8668        raw_name: &str,
8669    ) -> Option<CodeUnit> {
8670        let resolved = self.resolve_type(file, raw_name)?;
8671        self.alias_target(&resolved).or(Some(resolved))
8672    }
8673
8674    pub fn parser_alias_resolves_to_type(
8675        &self,
8676        file: &ProjectFile,
8677        raw_name: &str,
8678        target: &CodeUnit,
8679    ) -> bool {
8680        let Some(alias_name) = normalize_reference_name(raw_name) else {
8681            return false;
8682        };
8683        self.parser_alias_name_may_resolve_to_target(file, &alias_name, target)
8684    }
8685
8686    #[cfg(any(test, feature = "test-support"))]
8687    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8688        self.visible_source_files_by_root
8689            .get(file)
8690            .cloned()
8691            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8692    }
8693
8694    #[cfg(any(test, feature = "test-support"))]
8695    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8696        self.alias_source_parse_counts
8697            .lock()
8698            .expect("alias source parse count lock")
8699            .get(file)
8700            .copied()
8701            .unwrap_or(0)
8702    }
8703
8704    #[cfg(any(test, feature = "test-support"))]
8705    pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8706        self.parser_alias_fallback_files.load(Ordering::Relaxed)
8707    }
8708
8709    pub fn resolve_named(
8710        &self,
8711        file: &ProjectFile,
8712        raw_name: &str,
8713        kind: TargetKind,
8714    ) -> Option<CodeUnit> {
8715        let normalized = normalize_reference_name(raw_name)?;
8716        self.named_candidates_for_normalized(file, &normalized, kind)
8717            .into_iter()
8718            .next()
8719            .cloned()
8720    }
8721
8722    pub fn contains_named_symbol(
8723        &self,
8724        file: &ProjectFile,
8725        raw_name: &str,
8726        kind: TargetKind,
8727        target: &CodeUnit,
8728    ) -> bool {
8729        let Some(normalized) = normalize_reference_name(raw_name) else {
8730            return false;
8731        };
8732        self.named_candidates_for_normalized(file, &normalized, kind)
8733            .into_iter()
8734            .any(|unit| {
8735                matches_kind_for_lookup(unit, kind)
8736                    && reference_matches_unit(&normalized, unit)
8737                    && same_visible_symbol(unit, target)
8738            })
8739    }
8740
8741    pub fn named_candidates(
8742        &self,
8743        file: &ProjectFile,
8744        raw_name: &str,
8745        kind: TargetKind,
8746    ) -> Vec<CodeUnit> {
8747        let Some(normalized) = normalize_reference_name(raw_name) else {
8748            return Vec::new();
8749        };
8750        self.named_candidates_for_normalized(file, &normalized, kind)
8751            .into_iter()
8752            .cloned()
8753            .collect()
8754    }
8755
8756    pub fn resolve_known_non_target(
8757        &self,
8758        file: &ProjectFile,
8759        raw_name: &str,
8760        kind: TargetKind,
8761        target: &CodeUnit,
8762    ) -> bool {
8763        let Some(normalized) = normalize_reference_name(raw_name) else {
8764            return false;
8765        };
8766        normalized.contains("::")
8767            && self
8768                .named_candidates_for_normalized(file, &normalized, kind)
8769                .into_iter()
8770                .any(|unit| {
8771                    matches_kind_for_lookup(unit, kind)
8772                        && reference_matches_unit(&normalized, unit)
8773                        && !same_visible_symbol(unit, target)
8774                })
8775    }
8776
8777    pub fn resolve_call_return_binding(
8778        &self,
8779        analyzer: &CppGraphSource<'_>,
8780        file: &ProjectFile,
8781        raw_name: &str,
8782        arity: usize,
8783        lexical_namespace: Option<&str>,
8784        direct_type: Option<&CodeUnit>,
8785    ) -> Option<CppScanBinding> {
8786        let normalized = normalize_reference_name(raw_name)?;
8787        let mut candidates = Vec::new();
8788        for function in
8789            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8790        {
8791            if cpp_callable_arity(analyzer, function).accepts(arity)
8792                && !direct_type.is_some_and(|direct_type| {
8793                    self.callable_is_constructor_declaration(analyzer, function)
8794                        && type_owner_of(analyzer, function)
8795                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8796                })
8797            {
8798                candidates.push(function.clone());
8799            }
8800        }
8801        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8802        unanimous_return_binding(analyzer, self, file, &candidates)
8803    }
8804
8805    pub fn resolve_call_return_binding_without_arity(
8806        &self,
8807        analyzer: &CppGraphSource<'_>,
8808        file: &ProjectFile,
8809        raw_name: &str,
8810        lexical_namespace: Option<&str>,
8811        direct_type: Option<&CodeUnit>,
8812    ) -> (bool, Option<CppScanBinding>) {
8813        let Some(normalized) = normalize_reference_name(raw_name) else {
8814            return (false, None);
8815        };
8816        let mut candidates = self
8817            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8818            .into_iter()
8819            .filter(|function| {
8820                function.is_function()
8821                    && !direct_type.is_some_and(|direct_type| {
8822                        self.callable_is_constructor_declaration(analyzer, function)
8823                            && type_owner_of(analyzer, function)
8824                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8825                    })
8826            })
8827            .cloned()
8828            .collect::<Vec<_>>();
8829        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8830        let has_candidates = !candidates.is_empty();
8831        (
8832            has_candidates,
8833            unanimous_return_binding(analyzer, self, file, &candidates),
8834        )
8835    }
8836
8837    pub fn visible_identifier_candidates<'b>(
8838        &'b self,
8839        file: &ProjectFile,
8840        identifier: &str,
8841    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8842        self.visible_by_identifier
8843            .get(file)
8844            .and_then(|by_name| by_name.get(identifier))
8845            .into_iter()
8846            .flatten()
8847    }
8848
8849    /// Whether a grammar component is the direct spelling of `target`.
8850    ///
8851    /// Concrete template specializations are indexed with their arguments in
8852    /// `identifier`, while a reference's name component contains only the
8853    /// primary name. Argument matching remains the later resolver's job.
8854    pub fn type_reference_component_directly_names_target(
8855        &self,
8856        component: &str,
8857        target: &CodeUnit,
8858    ) -> bool {
8859        component == target.identifier()
8860            || self
8861                .cpp_template_metadata
8862                .get(target)
8863                .is_some_and(|metadata| component == metadata.primary_name)
8864    }
8865
8866    /// Return terminal reference names that can denote `target` from `file`.
8867    ///
8868    /// The indexed candidate table covers ordinary declarations and aliases;
8869    /// Parser-only aliases are tested lazily when their spelling is actually
8870    /// encountered in a scanned type node. Enumerating them here would parse
8871    /// every source in the include closure even when the target's direct name
8872    /// is the only spelling present in the file.
8873    pub fn visible_type_reference_component_names_for_target(
8874        &self,
8875        analyzer: &CppGraphSource<'_>,
8876        file: &ProjectFile,
8877        target: &CodeUnit,
8878    ) -> HashSet<String> {
8879        let mut names = HashSet::from_iter([target.identifier().to_string()]);
8880        if let Some(metadata) = self.cpp_template_metadata.get(target) {
8881            names.insert(metadata.primary_name.clone());
8882        }
8883
8884        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8885            for (identifier, candidates) in by_identifier {
8886                if candidates.iter().any(|candidate| {
8887                    (candidate.is_class()
8888                        && (same_visible_symbol(candidate, target)
8889                            || self.compatible_primary_template_redeclarations(candidate, target)))
8890                        || (declared_type_alias(analyzer, candidate)
8891                            && self.alias_candidate_structurally_reaches_target(
8892                                analyzer, file, candidate, target,
8893                            ))
8894                }) {
8895                    names.insert(identifier.clone());
8896                }
8897            }
8898        }
8899
8900        names
8901    }
8902
8903    pub fn indexed_structural_class_scope(
8904        &self,
8905        file: &ProjectFile,
8906        class: Node<'_>,
8907        source: &str,
8908    ) -> Option<Vec<String>> {
8909        let key = (file.clone(), class.start_byte(), class.end_byte());
8910        if let Some(cached) = self
8911            .indexed_structural_class_scopes
8912            .lock()
8913            .expect("C++ indexed structural-class scope cache poisoned")
8914            .get(&key)
8915            .cloned()
8916        {
8917            return cached;
8918        }
8919        let resolved = (|| {
8920            let name = class.child_by_field_name("name")?;
8921            let identifier = if name.kind() == "template_type" {
8922                node_text(name.child_by_field_name("name")?, source).to_string()
8923            } else {
8924                let mut components = Vec::new();
8925                append_cpp_name_components(name, source, &mut components)?;
8926                components.last()?.clone()
8927            };
8928            let visible = self
8929                .visible_identifier_candidates(file, &identifier)
8930                .cloned()
8931                .collect::<Vec<_>>();
8932            let mut visible = visible;
8933            for candidate in
8934                self.visible_by_file
8935                    .get(file)
8936                    .into_iter()
8937                    .flatten()
8938                    .filter(|candidate| {
8939                        self.cpp_template_metadata
8940                            .get(candidate)
8941                            .is_some_and(|metadata| metadata.primary_name == identifier)
8942                    })
8943            {
8944                if !visible
8945                    .iter()
8946                    .any(|existing| same_logical_symbol(existing, candidate))
8947                {
8948                    visible.push(candidate.clone());
8949                }
8950            }
8951            // Built once per call rather than per candidate; `cpp_source` rebuilds
8952            // the five-field source from the same `self.cpp` on every call.
8953            let cpp_source = self.cpp_source();
8954            let candidates = visible
8955                .iter()
8956                .filter(|candidate| {
8957                    candidate.source() == file
8958                        && candidate.is_class()
8959                        && !declared_type_alias(&cpp_source, candidate)
8960                        && self.cpp.ranges(candidate).iter().any(|range| {
8961                            range.start_byte <= class.start_byte()
8962                                && class.end_byte() <= range.end_byte
8963                        })
8964                })
8965                .collect::<Vec<_>>();
8966            let owner = if name.kind() == "template_type" {
8967                let expected = normalize_cpp_whitespace(node_text(name, source));
8968                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
8969                let exact = candidates
8970                    .iter()
8971                    .copied()
8972                    .filter(|candidate| {
8973                        candidate
8974                            .fq()
8975                            .segments()
8976                            .iter()
8977                            .rev()
8978                            .find_map(|&segment| {
8979                                let (text, kind) = interner.resolve(segment);
8980                                matches!(
8981                                    kind,
8982                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
8983                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
8984                                )
8985                                .then_some(text)
8986                            })
8987                            .is_some_and(|text| text == expected)
8988                    })
8989                    .collect::<Vec<_>>();
8990                unique_logical_type_candidate(exact)
8991                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
8992            } else {
8993                unique_logical_type_candidate(candidates)?
8994            };
8995            Some(canonical_cpp_scope_components(&owner))
8996        })();
8997        self.indexed_structural_class_scopes
8998            .lock()
8999            .expect("C++ indexed structural-class scope cache poisoned")
9000            .insert(key, resolved.clone());
9001        resolved
9002    }
9003
9004    pub fn indexed_enclosing_owner_scope(
9005        &self,
9006        analyzer: &CppGraphSource<'_>,
9007        file: &ProjectFile,
9008        node: Node<'_>,
9009    ) -> Option<Vec<String>> {
9010        let anchor = std::iter::successors(Some(node), |current| current.parent())
9011            .find(|current| {
9012                matches!(
9013                    current.kind(),
9014                    "function_definition"
9015                        | "class_specifier"
9016                        | "struct_specifier"
9017                        | "union_specifier"
9018                )
9019            })
9020            .unwrap_or(node);
9021        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9022        if let Some(cached) = self
9023            .indexed_enclosing_owner_scopes
9024            .lock()
9025            .expect("C++ indexed enclosing-owner scope cache poisoned")
9026            .get(&key)
9027            .cloned()
9028        {
9029            return cached;
9030        }
9031        let resolved = (|| {
9032            let range = Range {
9033                start_byte: node.start_byte(),
9034                end_byte: node.end_byte(),
9035                start_line: node.start_position().row,
9036                end_line: node.end_position().row,
9037            };
9038            let start = analyzer.enclosing_code_unit(file, &range)?;
9039            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9040                start,
9041                |unit| self.cached_precise_parent_of(analyzer, unit),
9042            )
9043            .find(|unit| {
9044                unit.is_class()
9045                    && !analyzer
9046                        .type_alias_provider()
9047                        .is_some_and(|provider| provider.is_type_alias(unit))
9048            })?;
9049            Some(canonical_cpp_scope_components(&owner))
9050        })();
9051        self.indexed_enclosing_owner_scopes
9052            .lock()
9053            .expect("C++ indexed enclosing-owner scope cache poisoned")
9054            .insert(key, resolved.clone());
9055        resolved
9056    }
9057
9058    fn cached_precise_parent_of(
9059        &self,
9060        analyzer: &CppGraphSource<'_>,
9061        code_unit: &CodeUnit,
9062    ) -> Option<CodeUnit> {
9063        if let Some(cached) = self
9064            .precise_parent_cache
9065            .lock()
9066            .expect("C++ precise-parent cache poisoned")
9067            .get(code_unit)
9068            .cloned()
9069        {
9070            return cached;
9071        }
9072        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9073        self.precise_parent_cache
9074            .lock()
9075            .expect("C++ precise-parent cache poisoned")
9076            .insert(code_unit.clone(), resolved.clone());
9077        resolved
9078    }
9079
9080    pub fn callable_is_constructor_declaration(
9081        &self,
9082        analyzer: &CppGraphSource<'_>,
9083        candidate: &CodeUnit,
9084    ) -> bool {
9085        if !candidate.is_function() {
9086            return false;
9087        }
9088        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9089            return false;
9090        };
9091        let root = prepared.tree().root_node();
9092        let candidate_ranges = analyzer.ranges(candidate);
9093        let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
9094            let mut current = root
9095                .descendant_for_byte_range(range.start_byte, range.end_byte)
9096                .and_then(|node| node.parent());
9097            while let Some(node) = current {
9098                if matches!(
9099                    node.kind(),
9100                    "class_specifier" | "struct_specifier" | "union_specifier"
9101                ) {
9102                    return node
9103                        .child_by_field_name("name")
9104                        .map(|name| terminal_name(node_text(name, prepared.source())))
9105                        .is_some_and(|name| name == candidate.identifier());
9106                }
9107                current = node.parent();
9108            }
9109            false
9110        });
9111        if enclosed_by_matching_type {
9112            return true;
9113        }
9114        let indexed_containment = analyzer
9115            .declarations(candidate.source())
9116            .into_iter()
9117            .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
9118            .any(|owner| {
9119                analyzer.ranges(&owner).iter().any(|owner_range| {
9120                    candidate_ranges.iter().any(|candidate_range| {
9121                        owner_range.start_byte <= candidate_range.start_byte
9122                            && candidate_range.end_byte <= owner_range.end_byte
9123                    })
9124                })
9125            });
9126        if indexed_containment {
9127            return true;
9128        }
9129        let metadata = analyzer.signature_metadata(candidate);
9130        !metadata.is_empty()
9131            && metadata
9132                .iter()
9133                .all(|signature| signature.return_type_text().is_none())
9134    }
9135
9136    /// Whether a callable declaration is a class-template deduction guide.
9137    ///
9138    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
9139    /// type field whose function declarator owns a trailing return type. This
9140    /// structured shape distinguishes a guide from both a constructor (no
9141    /// trailing return) and an ordinary trailing-return function (an `auto`
9142    /// type field).
9143    pub fn callable_is_deduction_guide_declaration(
9144        &self,
9145        analyzer: &CppGraphSource<'_>,
9146        candidate: &CodeUnit,
9147    ) -> bool {
9148        if !candidate.is_function() {
9149            return false;
9150        }
9151        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9152            return false;
9153        };
9154        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
9155            .into_iter()
9156            .any(|declaration| {
9157                if declaration.kind() != "declaration"
9158                    || declaration.child_by_field_name("type").is_some()
9159                {
9160                    return false;
9161                }
9162                let Some(declarator) = declaration.child_by_field_name("declarator") else {
9163                    return false;
9164                };
9165                if declarator.kind() != "function_declarator" {
9166                    return false;
9167                }
9168                let mut cursor = declarator.walk();
9169                let has_trailing_return = declarator
9170                    .named_children(&mut cursor)
9171                    .any(|child| child.kind() == "trailing_return_type");
9172                has_trailing_return
9173                    && declarator_name_node(declarator).is_some_and(|name| {
9174                        node_text(name, prepared.source()) == candidate.identifier()
9175                    })
9176            })
9177    }
9178
9179    /// Whether a callable occurrence is directly wrapped by a C++ template
9180    /// declaration. This deliberately inspects declaration syntax instead of
9181    /// inferring template status from the rendered signature.
9182    pub fn callable_is_template_declaration(
9183        &self,
9184        analyzer: &CppGraphSource<'_>,
9185        candidate: &CodeUnit,
9186    ) -> bool {
9187        if !candidate.is_function() {
9188            return false;
9189        }
9190        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9191            return false;
9192        };
9193        let root = prepared.tree().root_node();
9194        analyzer.ranges(candidate).iter().any(|range| {
9195            let Some(node) = node_for_exact_range(root, range)
9196                .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
9197            else {
9198                return false;
9199            };
9200            node.parent().is_some_and(|parent| {
9201                parent.kind() == "template_declaration"
9202                    && parent
9203                        .named_child(parent.named_child_count().saturating_sub(1))
9204                        .is_some_and(|declaration| same_node(declaration, node))
9205            })
9206        })
9207    }
9208
9209    pub fn type_name_candidates<'b>(
9210        &'b self,
9211        file: &ProjectFile,
9212        normalized: &str,
9213    ) -> Vec<&'b CodeUnit> {
9214        self.candidate_units(file, normalized, TargetKind::Type)
9215    }
9216
9217    pub fn visible_members_for_owner_name<'b>(
9218        &'b self,
9219        file: &ProjectFile,
9220        owner: &CodeUnit,
9221        name: &str,
9222    ) -> Vec<&'b CodeUnit> {
9223        self.visible_identifier_candidates(file, name)
9224            .filter(|unit| {
9225                // Structured owner pop on the unit's own `fq()` (shared with
9226                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
9227                // string.
9228                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9229                    .is_some_and(|parent| parent == owner.fq_name())
9230            })
9231            .collect()
9232    }
9233
9234    pub fn visible_member_for_owner_name(
9235        &self,
9236        file: &ProjectFile,
9237        owner: &CodeUnit,
9238        name: &str,
9239    ) -> VisibleMemberResolution {
9240        let candidates = self.visible_members_for_owner_name(file, owner, name);
9241        let mut callables = Vec::new();
9242        let mut non_callable = None;
9243        for candidate in candidates {
9244            if candidate.is_function() {
9245                callables.push(candidate.clone());
9246            } else if non_callable.is_none() {
9247                non_callable = Some(candidate.clone());
9248            }
9249        }
9250        match (callables.is_empty(), non_callable) {
9251            (false, None) => VisibleMemberResolution::Callable(callables),
9252            (true, Some(_)) => VisibleMemberResolution::NonCallable,
9253            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9254            (true, None) => VisibleMemberResolution::Missing,
9255        }
9256    }
9257
9258    fn field_declared_type_fact(
9259        &self,
9260        analyzer: &CppGraphSource<'_>,
9261        field: &CodeUnit,
9262    ) -> Option<DeclaredFieldTypeFact> {
9263        if let Some(cached) = self
9264            .field_type_facts
9265            .lock()
9266            .expect("C++ field type fact cache poisoned")
9267            .get(field)
9268            .cloned()
9269        {
9270            return cached;
9271        }
9272        let decoded = decode_field_declared_type_fact(analyzer, field);
9273        self.field_type_facts
9274            .lock()
9275            .expect("C++ field type fact cache poisoned")
9276            .insert(field.clone(), decoded.clone());
9277        decoded
9278    }
9279
9280    fn structured_alias_target(
9281        &self,
9282        analyzer: &CppGraphSource<'_>,
9283        unit: &CodeUnit,
9284    ) -> Option<StructuredAliasTarget> {
9285        if let Some(cached) = self
9286            .structured_alias_targets
9287            .lock()
9288            .expect("C++ structured alias target cache poisoned")
9289            .get(unit)
9290            .cloned()
9291        {
9292            return cached;
9293        }
9294        let decoded = decode_structured_alias_target(analyzer, unit);
9295        self.structured_alias_targets
9296            .lock()
9297            .expect("C++ structured alias target cache poisoned")
9298            .insert(unit.clone(), decoded.clone());
9299        decoded
9300    }
9301
9302    pub fn type_candidates<'b>(
9303        &'b self,
9304        file: &ProjectFile,
9305        normalized: &str,
9306    ) -> Vec<&'b CodeUnit> {
9307        let mut candidates = self
9308            .candidate_units(file, normalized, TargetKind::Type)
9309            .into_iter()
9310            .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
9311            .collect::<Vec<_>>();
9312        dedup_unit_refs(&mut candidates);
9313        candidates
9314    }
9315
9316    pub fn named_candidates_for_normalized<'b>(
9317        &'b self,
9318        file: &ProjectFile,
9319        normalized: &str,
9320        kind: TargetKind,
9321    ) -> Vec<&'b CodeUnit> {
9322        let mut candidates = self
9323            .candidate_units(file, normalized, kind)
9324            .into_iter()
9325            .filter(|unit| {
9326                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9327            })
9328            .collect::<Vec<_>>();
9329        dedup_unit_refs(&mut candidates);
9330        candidates
9331    }
9332
9333    pub fn candidate_units<'b>(
9334        &'b self,
9335        file: &ProjectFile,
9336        normalized: &str,
9337        kind: TargetKind,
9338    ) -> Vec<&'b CodeUnit> {
9339        if normalized.contains("::") {
9340            // `normalized` comes from `normalize_cpp_reference_text`, which
9341            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
9342            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
9343            // kept intact by the shared splitter's operator merge — the same
9344            // domain `cpp_reference_fqn_candidates` below already parses with
9345            // the shared splitter. Re-tokenizing and taking the last segment
9346            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
9347            // scan exactly.
9348            let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9349                brokk_bifrost_core::analyzer::Language::Cpp,
9350                normalized,
9351            )
9352            .pop() else {
9353                return Vec::new();
9354            };
9355            let fqns = cpp_reference_fqn_candidates(normalized, kind);
9356            return self
9357                .visible_identifier_candidates(file, &identifier)
9358                .filter(|unit| {
9359                    #[cfg(any(test, feature = "test-support"))]
9360                    self.qualified_candidate_inspections
9361                        .fetch_add(1, Ordering::Relaxed);
9362                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9363                        || canonical_cpp_name_matches(unit, normalized)
9364                })
9365                .collect();
9366        }
9367        self.visible_identifier_candidates(file, normalized)
9368            .collect()
9369    }
9370
9371    #[cfg(any(test, feature = "test-support"))]
9372    pub fn reset_qualified_candidate_inspections(&self) {
9373        self.qualified_candidate_inspections
9374            .store(0, Ordering::Relaxed);
9375    }
9376
9377    #[cfg(any(test, feature = "test-support"))]
9378    pub fn qualified_candidate_inspections(&self) -> usize {
9379        self.qualified_candidate_inspections.load(Ordering::Relaxed)
9380    }
9381
9382    #[cfg(any(test, feature = "test-support"))]
9383    pub fn visibility_identifier_lookup_count(&self) -> usize {
9384        self.visibility_identifier_lookup_count
9385    }
9386
9387    #[cfg(any(test, feature = "test-support"))]
9388    pub fn visibility_identifier_batch_count(&self) -> usize {
9389        self.visibility_identifier_batch_count
9390    }
9391
9392    #[cfg(any(test, feature = "test-support"))]
9393    pub fn reset_target_preserving_type_resolution_count(&self) {
9394        self.target_preserving_type_resolution_count
9395            .store(0, Ordering::Relaxed);
9396    }
9397
9398    #[cfg(any(test, feature = "test-support"))]
9399    pub fn target_preserving_type_resolution_count(&self) -> usize {
9400        self.target_preserving_type_resolution_count
9401            .load(Ordering::Relaxed)
9402    }
9403
9404    #[cfg(any(test, feature = "test-support"))]
9405    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9406        self.visible_parser_alias_name_set_build_count
9407            .load(Ordering::Relaxed)
9408    }
9409}
9410
9411#[derive(Default)]
9412struct IncludeGraph {
9413    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9414}
9415
9416impl IncludeGraph {
9417    fn extend_with<F>(
9418        &mut self,
9419        root: &ProjectFile,
9420        cancellation: Option<&CancellationToken>,
9421        targets_for: &mut F,
9422    ) where
9423        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9424    {
9425        let mut stack = vec![root.clone()];
9426        while let Some(file) = stack.pop() {
9427            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9428                break;
9429            }
9430            if self.targets_by_file.contains_key(&file) {
9431                continue;
9432            }
9433            let targets = targets_for(&file);
9434            stack.extend(targets.iter().cloned());
9435            self.targets_by_file.insert(file, targets);
9436        }
9437    }
9438
9439    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9440        self.targets_by_file.keys()
9441    }
9442
9443    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9444        self.targets_by_file
9445            .get(file)
9446            .map(Vec::as_slice)
9447            .unwrap_or_default()
9448    }
9449
9450    fn reachable_files(
9451        &self,
9452        root: &ProjectFile,
9453        cancellation: Option<&CancellationToken>,
9454    ) -> HashSet<ProjectFile> {
9455        let mut pending = vec![root.clone()];
9456        let mut visited = HashSet::default();
9457        while let Some(file) = pending.pop() {
9458            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9459                break;
9460            }
9461            if visited.insert(file.clone()) {
9462                pending.extend(self.targets(&file).iter().cloned());
9463            }
9464        }
9465        visited
9466    }
9467}
9468
9469fn build_bounded_visible_declarations(
9470    cpp: &dyn CppSource,
9471    token: QueryToken<'_>,
9472    analyzer: &CppGraphSource<'_>,
9473    roots: &HashSet<ProjectFile>,
9474    visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9475    cancellation: Option<&CancellationToken>,
9476    stats: &mut BoundedVisibilityStats,
9477) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9478    let mut candidates_by_identifier = HashMap::default();
9479    let mut declarations_by_source_and_reading: HashMap<
9480        (ProjectFile, bool),
9481        BoundedVisibilityDeclarations,
9482    > = HashMap::default();
9483    let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9484    roots
9485        .iter()
9486        .map(|root| {
9487            let reading_is_c = analyzer.reference_uses_c_semantics(root);
9488            let root_declarations = declarations_by_source_and_reading
9489                .entry((root.clone(), reading_is_c))
9490                .or_insert_with(|| {
9491                    bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9492                });
9493            let mut visible = root_declarations
9494                .all
9495                .iter()
9496                .cloned()
9497                .collect::<HashSet<_>>();
9498            let mut pending_names = HashSet::default();
9499            if let Some(prepared) = cpp.prepared_syntax(token, root) {
9500                // One cursor for the whole file walk: `named_child(index)`
9501                // re-steps the sibling list from the first child on every
9502                // access, which made this whole-file scan quadratic in the
9503                // fan-out of each node (#3097).
9504                let mut cursor = prepared.tree().walk();
9505                let mut pending_nodes = vec![prepared.tree().root_node()];
9506                while let Some(node) = pending_nodes.pop() {
9507                    if matches!(
9508                        node.kind(),
9509                        "identifier"
9510                            | "type_identifier"
9511                            | "field_identifier"
9512                            | "namespace_identifier"
9513                    ) {
9514                        pending_names.insert(node_text(node, prepared.source()).to_string());
9515                    }
9516                    if node.kind() == "preproc_arg" {
9517                        for reference in
9518                            object_macro_replacement_type_references(node, prepared.source())
9519                        {
9520                            pending_names.extend(reference.components);
9521                        }
9522                    }
9523                    pending_nodes.extend(node.named_children(&mut cursor));
9524                }
9525            }
9526            stats.root_names += pending_names.len();
9527            let mut completed_names = HashSet::default();
9528            while !pending_names.is_empty() {
9529                stats.rounds += 1;
9530                let round_names = std::mem::take(&mut pending_names);
9531                let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9532                    HashMap::default();
9533                let mut identifiers = Vec::new();
9534                for identifier in round_names {
9535                    if !completed_names.insert(identifier.clone())
9536                        || cancellation.is_some_and(CancellationToken::is_cancelled)
9537                    {
9538                        continue;
9539                    }
9540                    identifiers.push(identifier);
9541                }
9542                let missing_identifiers = identifiers
9543                    .iter()
9544                    .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9545                    .cloned()
9546                    .collect::<HashSet<_>>();
9547                if !missing_identifiers.is_empty() {
9548                    let lookup_started = Instant::now();
9549                    let mut candidates = cpp
9550                        .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9551                    stats.lookup_elapsed += lookup_started.elapsed();
9552                    stats.identifier_lookups += missing_identifiers.len();
9553                    stats.identifier_batches += 1;
9554                    if cancellation.is_some_and(CancellationToken::is_cancelled) {
9555                        break;
9556                    }
9557                    for identifier in missing_identifiers {
9558                        let units = candidates.remove(&identifier).unwrap_or_default();
9559                        let candidate_count = units.len();
9560                        let candidate_sources = units
9561                            .into_iter()
9562                            .map(|unit| unit.source().clone())
9563                            .collect::<HashSet<_>>();
9564                        candidates_by_identifier
9565                            .insert(identifier, (candidate_sources, candidate_count));
9566                    }
9567                }
9568                for identifier in identifiers {
9569                    let (candidate_sources, candidate_count) = candidates_by_identifier
9570                        .get(&identifier)
9571                        .expect("every missing identifier was inserted after the batch lookup");
9572                    stats.candidate_units += *candidate_count;
9573                    for source in candidate_sources.iter().cloned() {
9574                        if source != *root
9575                            && visible_sources
9576                                .get(root)
9577                                .is_some_and(|files| files.contains(&source))
9578                        {
9579                            requested_names_by_source
9580                                .entry(source)
9581                                .or_default()
9582                                .insert(identifier.clone());
9583                        }
9584                    }
9585                }
9586                stats.candidate_sources += requested_names_by_source.len();
9587                for (source, requested_names) in requested_names_by_source {
9588                    let declarations = declarations_by_source_and_reading
9589                        .entry((source.clone(), reading_is_c))
9590                        .or_insert_with(|| {
9591                            bounded_visibility_declarations(
9592                                cpp,
9593                                analyzer,
9594                                &source,
9595                                reading_is_c,
9596                                stats,
9597                            )
9598                        });
9599                    let mut selected = HashSet::default();
9600                    for name in requested_names {
9601                        if let Some(units) = declarations.by_identifier.get(&name) {
9602                            selected.extend(units.iter().cloned());
9603                        }
9604                    }
9605                    for unit in selected {
9606                        stats.selected_units += 1;
9607                        let dependency_names = dependency_names_by_unit
9608                            .entry(unit.clone())
9609                            .or_insert_with(|| {
9610                                let mut dependency_names = HashSet::default();
9611                                if let Some(prepared) = cpp.prepared_syntax(token, &source) {
9612                                    let ast_started = Instant::now();
9613                                    let mut cursor = prepared.tree().walk();
9614                                    for range in analyzer.ranges(&unit) {
9615                                        let Some(declaration) = node_for_exact_range(
9616                                            prepared.tree().root_node(),
9617                                            &range,
9618                                        ) else {
9619                                            continue;
9620                                        };
9621                                        let mut pending_nodes = vec![declaration];
9622                                        while let Some(node) = pending_nodes.pop() {
9623                                            stats.dependency_ast_nodes += 1;
9624                                            #[cfg(any(test, feature = "test-support"))]
9625                                            BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
9626                                                .with(|count| count.set(count.get() + 1));
9627                                            if matches!(
9628                                                node.kind(),
9629                                                "type_identifier" | "namespace_identifier"
9630                                            ) {
9631                                                dependency_names.insert(
9632                                                    node_text(node, prepared.source()).into(),
9633                                                );
9634                                            }
9635                                            pending_nodes.extend(node.named_children(&mut cursor));
9636                                        }
9637                                    }
9638                                    stats.dependency_ast_elapsed += ast_started.elapsed();
9639                                }
9640                                dependency_names
9641                            });
9642                        for name in dependency_names.iter() {
9643                            if !completed_names.contains(name) && pending_names.insert(name.clone())
9644                            {
9645                                stats.dependency_names += 1;
9646                            }
9647                        }
9648                        visible.insert(unit);
9649                    }
9650                }
9651            }
9652            (root.clone(), visible)
9653        })
9654        .collect()
9655}
9656
9657struct BoundedVisibilityDeclarations {
9658    all: Vec<CodeUnit>,
9659    by_identifier: HashMap<String, Vec<CodeUnit>>,
9660}
9661
9662fn bounded_visibility_declarations(
9663    cpp: &dyn CppSource,
9664    analyzer: &CppGraphSource<'_>,
9665    file: &ProjectFile,
9666    c_semantics: bool,
9667    stats: &mut BoundedVisibilityStats,
9668) -> BoundedVisibilityDeclarations {
9669    let declarations_started = Instant::now();
9670    let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
9671        .into_iter()
9672        .collect::<Vec<_>>();
9673    stats.declaration_elapsed += declarations_started.elapsed();
9674    stats.declaration_reads += 1;
9675    stats.declaration_units += all.len();
9676
9677    let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9678    for unit in &all {
9679        by_identifier
9680            .entry(unit.identifier().to_string())
9681            .or_default()
9682            .push(unit.clone());
9683        if unit.is_class()
9684            && let Some(metadata) = cpp.template_metadata(unit)
9685            && metadata.primary_name != unit.identifier()
9686        {
9687            by_identifier
9688                .entry(metadata.primary_name)
9689                .or_default()
9690                .push(unit.clone());
9691        }
9692    }
9693    BoundedVisibilityDeclarations { all, by_identifier }
9694}
9695
9696#[derive(Default)]
9697struct BoundedVisibilityStats {
9698    rounds: usize,
9699    root_names: usize,
9700    identifier_lookups: usize,
9701    identifier_batches: usize,
9702    candidate_units: usize,
9703    candidate_sources: usize,
9704    declaration_reads: usize,
9705    declaration_units: usize,
9706    selected_units: usize,
9707    dependency_ast_nodes: usize,
9708    dependency_names: usize,
9709    lookup_elapsed: Duration,
9710    declaration_elapsed: Duration,
9711    dependency_ast_elapsed: Duration,
9712}
9713
9714fn bounded_visibility_declarations_in_reading(
9715    analyzer: &CppGraphSource<'_>,
9716    file: &ProjectFile,
9717    c_semantics: bool,
9718) -> BTreeSet<CodeUnit> {
9719    #[cfg(any(test, feature = "test-support"))]
9720    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9721    analyzer.declarations_in_reading(file, c_semantics)
9722}
9723
9724#[cfg(any(test, feature = "test-support"))]
9725pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9726    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9727}
9728
9729#[cfg(any(test, feature = "test-support"))]
9730pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9731    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9732}
9733
9734#[cfg(any(test, feature = "test-support"))]
9735pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
9736    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
9737}
9738
9739#[cfg(any(test, feature = "test-support"))]
9740pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
9741    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
9742}
9743
9744pub struct VisibilityData {
9745    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9746    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9747}
9748
9749/// Build the per-root include closure and the declarations each root can see
9750/// through it.
9751///
9752/// `declarations_for` takes the reading to answer in (issue #1970): a root
9753/// compiled as C sees the C reading of every file in its closure, a root
9754/// compiled as C++ sees the C++ reading, and `reading_is_c_for` decides which
9755/// per root. The two readings agree for all but a handful of headers, so the
9756/// C map is built only when some root actually asks for it, and only over the
9757/// files that root reaches.
9758pub fn build_visibility_data<F, R, D>(
9759    roots: &HashSet<ProjectFile>,
9760    cancellation: Option<&CancellationToken>,
9761    mut targets_for: F,
9762    mut reading_is_c_for: R,
9763    mut declarations_for: D,
9764) -> VisibilityData
9765where
9766    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9767    R: FnMut(&ProjectFile) -> bool,
9768    D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9769{
9770    let mut include_graph = IncludeGraph::default();
9771    for file in roots {
9772        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9773            break;
9774        }
9775        include_graph.extend_with(file, cancellation, &mut targets_for);
9776    }
9777    let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9778        .files()
9779        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9780        .map(|file| (file.clone(), declarations_for(file, false)))
9781        .collect();
9782    let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9783    let mut visible_by_file = HashMap::default();
9784    let mut visible_source_files_by_root = HashMap::default();
9785    for file in roots {
9786        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9787            break;
9788        }
9789        let mut visited = HashSet::default();
9790        let mut visible = HashSet::default();
9791        let declarations_by_file = if reading_is_c_for(file) {
9792            for reached in cpp_declarations_by_file.keys() {
9793                if !c_declarations_by_file.contains_key(reached) {
9794                    let declarations = declarations_for(reached, true);
9795                    c_declarations_by_file.insert(reached.clone(), declarations);
9796                }
9797            }
9798            &c_declarations_by_file
9799        } else {
9800            &cpp_declarations_by_file
9801        };
9802        collect_visible_declarations(
9803            &include_graph,
9804            declarations_by_file,
9805            file,
9806            &mut visited,
9807            &mut visible,
9808            cancellation,
9809        );
9810        visible_by_file.insert(file.clone(), visible);
9811        visible_source_files_by_root.insert(file.clone(), visited);
9812    }
9813    VisibilityData {
9814        visible_by_file,
9815        visible_source_files_by_root,
9816    }
9817}
9818
9819/// Admit the class that an out-of-line definition proves is in scope.
9820///
9821/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
9822/// names a class-like entity in that file's scope: a member declaration can
9823/// live in a file other than its class's only when it is written out of line.
9824/// A file a build concatenates rather than compiles carries no `#include` edge
9825/// to the header declaring `Owner` -- google/wuffs
9826/// `internal/cgen/auxiliary/image.cc` defines
9827/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
9828/// every unqualified member and constructor reference in it had no candidate at
9829/// all (#1832).
9830///
9831/// The evidence is the indexed declaration's own owner name, taken from its
9832/// `FqName`, so this stays a structured answer rather than a text fallback.
9833/// Only an owner the file cannot already see is admitted: that is what keeps a
9834/// header declaring its own class from additionally seeing every same-named
9835/// class in the workspace, and it makes the pass free for the ordinary file
9836/// whose owners are all visible.
9837#[derive(Default)]
9838struct OutOfLineOwnerBindingStats {
9839    unseen_owners: usize,
9840    definition_lookups: usize,
9841    admitted: usize,
9842}
9843
9844fn extend_with_out_of_line_owner_bindings(
9845    cpp: &dyn CppSource,
9846    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9847) -> OutOfLineOwnerBindingStats {
9848    let mut stats = OutOfLineOwnerBindingStats::default();
9849    for (file, visible) in visible_by_file.iter_mut() {
9850        // The include-closure walk seeds every root with its own declarations,
9851        // so the file's members are already here; re-reading them from the
9852        // analyzer would pay for the same declaration set twice.
9853        let mut unseen_owners: HashSet<String> = visible
9854            .iter()
9855            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9856            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9857            .collect();
9858        if unseen_owners.is_empty() {
9859            continue;
9860        }
9861        for unit in visible.iter().filter(|unit| unit.is_class()) {
9862            unseen_owners.remove(&unit.fq_name());
9863        }
9864        stats.unseen_owners += unseen_owners.len();
9865        stats.definition_lookups += unseen_owners.len();
9866        let admitted = unseen_owners
9867            .iter()
9868            .flat_map(|owner| cpp.definitions(owner))
9869            .filter(CodeUnit::is_class)
9870            .collect::<Vec<_>>();
9871        stats.admitted += admitted.len();
9872        visible.extend(admitted);
9873    }
9874    stats
9875}
9876
9877pub enum VisibleMemberResolution {
9878    Callable(Vec<CodeUnit>),
9879    NonCallable,
9880    AmbiguousKind,
9881    Missing,
9882}
9883
9884#[derive(Clone)]
9885pub enum EnclosingMemberOwnerResolution {
9886    Owner(CodeUnit),
9887    Ambiguous,
9888    Missing,
9889}
9890
9891pub fn resolve_declaring_member_owner(
9892    analyzer: &CppGraphSource<'_>,
9893    visibility: &VisibilityIndex<'_>,
9894    file: &ProjectFile,
9895    receiver_owner: &CodeUnit,
9896    member_name: &str,
9897) -> EnclosingMemberOwnerResolution {
9898    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9899        return EnclosingMemberOwnerResolution::Missing;
9900    };
9901    let Some(receiver_owner) =
9902        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9903    else {
9904        return EnclosingMemberOwnerResolution::Ambiguous;
9905    };
9906    let resolve_level = |frontier: &[CodeUnit]| {
9907        let mut member_owners = Vec::new();
9908        for raw_owner in frontier {
9909            let Some(owner) =
9910                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9911            else {
9912                return EnclosingMemberOwnerResolution::Ambiguous;
9913            };
9914            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9915                // A type nested in an owner shares the owner's member-name
9916                // index, but it cannot be the value receiver of `owner.name`.
9917                // Keep value members here so an anonymous aggregate field
9918                // does not become ambiguous with its promoted receiver type.
9919                if !member.is_field() && !member.is_function() {
9920                    continue;
9921                }
9922                let Some(member_owner) = type_owner_of(analyzer, member) else {
9923                    return EnclosingMemberOwnerResolution::Ambiguous;
9924                };
9925                if !member_owners
9926                    .iter()
9927                    .any(|existing| same_visible_symbol(existing, &member_owner))
9928                {
9929                    member_owners.push(member_owner);
9930                }
9931            }
9932        }
9933        match member_owners.len() {
9934            0 => EnclosingMemberOwnerResolution::Missing,
9935            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9936            _ => EnclosingMemberOwnerResolution::Ambiguous,
9937        }
9938    };
9939    // The first declaration on each structured base path hides deeper names,
9940    // regardless of whether its callable overload is applicable at a particular
9941    // call site. Applicability is checked only after this owner is established.
9942    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9943    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9944        return direct;
9945    }
9946    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9947    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9948    let mut path_matches = Vec::new();
9949    while let Some(raw_owner) = stack.pop() {
9950        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9951        else {
9952            return EnclosingMemberOwnerResolution::Ambiguous;
9953        };
9954        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
9955        // Propagate at most two occurrences of each owner: that preserves the distinction
9956        // between one and multiple resolving base paths without exponential diamond walks.
9957        let propagated = propagated_counts.entry(owner.clone()).or_default();
9958        if *propagated == 2 {
9959            continue;
9960        }
9961        *propagated += 1;
9962        match resolve_level(std::slice::from_ref(&owner)) {
9963            EnclosingMemberOwnerResolution::Owner(owner) => {
9964                path_matches.push(owner);
9965                if path_matches.len() == 2 {
9966                    return EnclosingMemberOwnerResolution::Ambiguous;
9967                }
9968            }
9969            EnclosingMemberOwnerResolution::Ambiguous => {
9970                return EnclosingMemberOwnerResolution::Ambiguous;
9971            }
9972            EnclosingMemberOwnerResolution::Missing => {
9973                stack.extend(hierarchy.get_direct_ancestors(&owner));
9974            }
9975        }
9976    }
9977    match path_matches.len() {
9978        0 => EnclosingMemberOwnerResolution::Missing,
9979        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
9980        _ => unreachable!("base-path matches are capped at one before returning"),
9981    }
9982}
9983
9984/// Resolve the declaring owner of a callable after applying a member
9985/// `using <Base>::<member>;` declaration to one exact call arity.
9986///
9987/// Ordinary member lookup is intentionally name-based: the first class that
9988/// declares a name hides the same name on deeper bases. A member
9989/// using-declaration is the one exception. When none of the declarations on
9990/// that first owner accepts the call arity, it can reintroduce an applicable
9991/// overload from the named base. If a declaration on the first owner does
9992/// accept the arity, argument types would be needed to choose between it and
9993/// a same-arity introduced overload, so this resolver conservatively keeps the
9994/// ordinary owner (#1835/#1843).
9995///
9996/// The caller supplies ordinary name-based owner resolution so a file scan can
9997/// reuse its existing owner cache before applying this callable-only exception.
9998pub fn resolve_declaring_callable_owner(
9999    analyzer: &CppGraphSource<'_>,
10000    visibility: &VisibilityIndex<'_>,
10001    file: &ProjectFile,
10002    ordinary: EnclosingMemberOwnerResolution,
10003    member_name: &str,
10004    call_arity: usize,
10005) -> EnclosingMemberOwnerResolution {
10006    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
10007        return ordinary;
10008    };
10009    if visibility
10010        .visible_members_for_owner_name(file, ordinary_owner, member_name)
10011        .into_iter()
10012        .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
10013    {
10014        return ordinary;
10015    }
10016
10017    let mut pending = match member_using_declaration_bases(
10018        analyzer,
10019        visibility,
10020        file,
10021        ordinary_owner,
10022        member_name,
10023    ) {
10024        Ok(bases) => bases,
10025        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10026    };
10027    let mut visited = HashSet::default();
10028    let mut introduced_owners = Vec::new();
10029    while let Some(owner) = pending.pop() {
10030        if !visited.insert(owner.clone()) {
10031            continue;
10032        }
10033        let accepts_arity = visibility
10034            .visible_members_for_owner_name(file, &owner, member_name)
10035            .into_iter()
10036            .any(|unit| {
10037                unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
10038            });
10039        if accepts_arity {
10040            if !introduced_owners
10041                .iter()
10042                .any(|existing| same_visible_symbol(existing, &owner))
10043            {
10044                introduced_owners.push(owner);
10045            }
10046            continue;
10047        }
10048        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10049            Ok(bases) => pending.extend(bases),
10050            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10051        }
10052    }
10053    match introduced_owners.as_slice() {
10054        [] => ordinary,
10055        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10056        _ => EnclosingMemberOwnerResolution::Ambiguous,
10057    }
10058}
10059
10060fn member_using_declaration_bases(
10061    analyzer: &CppGraphSource<'_>,
10062    visibility: &VisibilityIndex<'_>,
10063    file: &ProjectFile,
10064    owner: &CodeUnit,
10065    member_name: &str,
10066) -> Result<Vec<CodeUnit>, ()> {
10067    let Some(source) = analyzer.get_source(owner, false) else {
10068        return Ok(Vec::new());
10069    };
10070    let scopes = cpp_member_using_declaration_scopes(&source, member_name);
10071    if scopes.is_empty() {
10072        return Ok(Vec::new());
10073    }
10074    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10075        return Ok(Vec::new());
10076    };
10077    let mut bases = Vec::new();
10078    for raw_ancestor in hierarchy.get_ancestors(owner) {
10079        let Some(ancestor) =
10080            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10081        else {
10082            return Err(());
10083        };
10084        let qualified = cpp_name_for(&ancestor);
10085        if scopes
10086            .iter()
10087            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10088            && !bases
10089                .iter()
10090                .any(|existing| same_visible_symbol(existing, &ancestor))
10091        {
10092            bases.push(ancestor);
10093        }
10094    }
10095    Ok(bases)
10096}
10097
10098pub fn lexical_component_tiers<'a>(
10099    components: &'a [String],
10100    global: bool,
10101    lexical_scope: &'a [String],
10102) -> impl Iterator<Item = Vec<String>> + 'a {
10103    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10104    (0..=first_prefix_len).rev().map(move |prefix_len| {
10105        let mut qualified = Vec::with_capacity(prefix_len + components.len());
10106        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10107        qualified.extend_from_slice(components);
10108        qualified
10109    })
10110}
10111
10112pub fn build_visible_identifier_index(
10113    analyzer: &CppGraphSource<'_>,
10114    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10115    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10116    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10117) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10118    let mut out = HashMap::default();
10119    for (file, visible) in visible_by_file {
10120        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10121        for unit in visible {
10122            if unit.is_field()
10123                && !visible_source_files_by_root
10124                    .get(file)
10125                    .is_some_and(|sources| sources.contains(unit.source()))
10126                && cpp_global_field_has_internal_linkage_cached(
10127                    analyzer,
10128                    global_field_internal_linkage,
10129                    unit,
10130                )
10131            {
10132                continue;
10133            }
10134            by_identifier
10135                .entry(unit.identifier().to_string())
10136                .or_default()
10137                .push(unit.clone());
10138        }
10139        for units in by_identifier.values_mut() {
10140            sort_lookup_units(units);
10141            units.dedup();
10142        }
10143        out.insert(file.clone(), by_identifier);
10144    }
10145    out
10146}
10147
10148fn sort_lookup_units(units: &mut [CodeUnit]) {
10149    units.sort_by(|left, right| {
10150        left.fq_name()
10151            .cmp(&right.fq_name())
10152            .then_with(|| left.signature().cmp(&right.signature()))
10153            .then_with(|| left.source().cmp(right.source()))
10154            .then_with(|| left.kind().cmp(&right.kind()))
10155            .then_with(|| {
10156                left.package_segment_count()
10157                    .cmp(&right.package_segment_count())
10158            })
10159            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10160            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10161    });
10162}
10163
10164fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10165    let interner = segment_interner();
10166    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10167        let (left_text, left_kind) = interner.resolve(left_id);
10168        let (right_text, right_kind) = interner.resolve(right_id);
10169        let order = left_text
10170            .cmp(right_text)
10171            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10172        if order != CmpOrdering::Equal {
10173            return order;
10174        }
10175    }
10176    left.len().cmp(&right.len())
10177}
10178
10179const fn segment_kind_order(kind: SegmentKind) -> u8 {
10180    match kind {
10181        SegmentKind::Path => 0,
10182        SegmentKind::Package => 1,
10183        SegmentKind::Type => 2,
10184        SegmentKind::Companion => 3,
10185        SegmentKind::Nested => 4,
10186        SegmentKind::Member => 5,
10187        SegmentKind::Unknown => 6,
10188    }
10189}
10190
10191fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10192    let mut deduped = Vec::with_capacity(units.len());
10193    for unit in units.drain(..) {
10194        if !deduped.contains(&unit) {
10195            deduped.push(unit);
10196        }
10197    }
10198    *units = deduped;
10199}
10200
10201pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10202    // Same domain as `candidate_units` above: `reference` is a plain
10203    // `::`-joined qualified-id with operator tokens kept intact by the shared
10204    // splitter's operator merge.
10205    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10206        brokk_bifrost_core::analyzer::Language::Cpp,
10207        reference,
10208    );
10209    if parts.is_empty() {
10210        return Vec::new();
10211    }
10212
10213    let mut candidates = Vec::new();
10214    for package_len in 0..parts.len() {
10215        let package = parts[..package_len].join("::");
10216        let rest = &parts[package_len..];
10217        if rest.is_empty() {
10218            continue;
10219        }
10220        match kind {
10221            TargetKind::Type | TargetKind::Constructor => {
10222                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10223                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10224            }
10225            TargetKind::FreeFunction
10226            | TargetKind::Method
10227            | TargetKind::GlobalField
10228            | TargetKind::MemberField
10229            | TargetKind::Macro => {
10230                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10231                if rest.len() > 1 {
10232                    let owner = rest[..rest.len() - 1].join("$");
10233                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10234                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
10235                }
10236            }
10237        }
10238    }
10239    candidates
10240}
10241
10242fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10243    let fqn = if package.is_empty() {
10244        short.to_string()
10245    } else {
10246        format!("{package}.{short}")
10247    };
10248    if !out.contains(&fqn) {
10249        out.push(fqn);
10250    }
10251}
10252
10253pub fn infer_cpp_initializer_type(
10254    analyzer: &CppGraphSource<'_>,
10255    visibility: &VisibilityIndex<'_>,
10256    file: &ProjectFile,
10257    source: &str,
10258    node: Node<'_>,
10259) -> Option<CodeUnit> {
10260    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10261        .and_then(|binding| binding.unit)
10262}
10263
10264pub fn infer_cpp_initializer_binding(
10265    analyzer: &CppGraphSource<'_>,
10266    visibility: &VisibilityIndex<'_>,
10267    file: &ProjectFile,
10268    source: &str,
10269    node: Node<'_>,
10270    receiver_resolver: Option<&ReceiverResolver<'_>>,
10271) -> Option<CppScanBinding> {
10272    match node.kind() {
10273        "new_expression" => {
10274            let text = normalize_cpp_whitespace(node_text(node, source));
10275            let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
10276            let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
10277            let name = normalize_cpp_type_name(type_text);
10278            Some(CppScanBinding::from_type_name(
10279                name.clone(),
10280                visibility.resolve_type(file, &name),
10281                1,
10282            ))
10283        }
10284        "call_expression" => node.child_by_field_name("function").and_then(|function| {
10285            // `a().b()` and `p->b()` invoke a member on a receiver *value*. The
10286            // callee's source text is an expression, not a name, and every name
10287            // lookup below normalizes a reference by truncating at the first
10288            // `(`: `first().second` would read as `first`, so the chained call
10289            // would take the type of `first()` instead of the type of
10290            // `first().second()` (#2178). Only the member path can answer for
10291            // this shape, so route to it from the callee's node kind.
10292            if function.kind() == "field_expression" {
10293                let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10294                return resolve_field_method_call_return_binding(
10295                    analyzer,
10296                    visibility,
10297                    file,
10298                    source,
10299                    function,
10300                    arity,
10301                    receiver_resolver,
10302                );
10303            }
10304            let function_text = node_text(function, source);
10305            let direct_type_binding = visibility
10306                .resolve_type(file, function_text)
10307                .map(|unit| CppScanBinding::from_unit(unit, 0));
10308            if function.kind() == "template_function" && direct_type_binding.is_some() {
10309                let lexical_namespace = enclosing_namespace_context(node, source);
10310                let arity = visibility.call_arity_evidence(file, node, source).exact();
10311                if let Some(arity) = arity
10312                    && let Some(binding) = visibility.resolve_call_return_binding(
10313                        analyzer,
10314                        file,
10315                        function_text,
10316                        arity,
10317                        lexical_namespace.as_deref(),
10318                        direct_type_binding
10319                            .as_ref()
10320                            .and_then(|binding| binding.unit.as_ref()),
10321                    )
10322                {
10323                    return Some(binding);
10324                }
10325                let (has_callable, callable_binding) = visibility
10326                    .resolve_call_return_binding_without_arity(
10327                        analyzer,
10328                        file,
10329                        function_text,
10330                        lexical_namespace.as_deref(),
10331                        direct_type_binding
10332                            .as_ref()
10333                            .and_then(|binding| binding.unit.as_ref()),
10334                    );
10335                if let Some(binding) = callable_binding {
10336                    return Some(binding);
10337                }
10338                if has_callable {
10339                    return None;
10340                }
10341                return direct_type_binding;
10342            }
10343            // Only the return-typed branches need the argument count. An
10344            // unknown arity leaves them out, exactly as in the template arm
10345            // above, and still constructs the direct type: `File(getPath())`
10346            // names `File` whether or not `getPath()`'s expansion is provable.
10347            let arity = visibility.call_arity_evidence(file, node, source).exact();
10348            if let Some(arity) = arity {
10349                let direct_type_binding_for_call = direct_type_binding.clone();
10350                if let Some(binding) = resolve_static_method_call_return_binding(
10351                    analyzer, visibility, file, source, function, arity,
10352                )
10353                .or_else(|| {
10354                    // An applicable free function supplies the receiver value
10355                    // before an unrelated visible type with the same terminal
10356                    // name. The direct type still excludes its own constructor
10357                    // declaration below and remains the construction fallback.
10358                    visibility.resolve_call_return_binding(
10359                        analyzer,
10360                        file,
10361                        function_text,
10362                        arity,
10363                        enclosing_namespace_context(node, source).as_deref(),
10364                        direct_type_binding_for_call
10365                            .as_ref()
10366                            .and_then(|binding| binding.unit.as_ref()),
10367                    )
10368                }) {
10369                    return Some(binding);
10370                }
10371            }
10372            direct_type_binding
10373        }),
10374        _ => None,
10375    }
10376}
10377
10378fn resolve_static_method_call_return_binding(
10379    analyzer: &CppGraphSource<'_>,
10380    visibility: &VisibilityIndex<'_>,
10381    file: &ProjectFile,
10382    source: &str,
10383    function: Node<'_>,
10384    arity: usize,
10385) -> Option<CppScanBinding> {
10386    if function.kind() != "qualified_identifier" {
10387        return None;
10388    }
10389    let qualified = normalize_cpp_reference_text(node_text(function, source));
10390    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
10391    // single component (the shared splitter's operator-token merge keeps
10392    // `operator+`-style names intact), so re-tokenizing with the shared
10393    // structured splitter and peeling the terminal segment reproduces
10394    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
10395    // `cpp_out_of_line_function_owner`'s `qualified` split above.
10396    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10397        brokk_bifrost_core::analyzer::Language::Cpp,
10398        &qualified,
10399    );
10400    let (owner_text, member_name) = match parts.split_last() {
10401        Some((member, owner_parts)) if !owner_parts.is_empty() => {
10402            (owner_parts.join("::"), member.clone())
10403        }
10404        _ => {
10405            let scope = function.child_by_field_name("scope")?;
10406            let name = function.child_by_field_name("name")?;
10407            (
10408                node_text(scope, source).to_string(),
10409                node_text(name, source).to_string(),
10410            )
10411        }
10412    };
10413    let owner = visibility.resolve_type(file, &owner_text)?;
10414    let candidates = visibility
10415        .visible_members_for_owner_name(file, &owner, &member_name)
10416        .into_iter()
10417        .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
10418        .cloned()
10419        .collect::<Vec<_>>();
10420    unanimous_return_binding(analyzer, visibility, file, &candidates)
10421}
10422
10423fn resolve_field_method_call_return_binding(
10424    analyzer: &CppGraphSource<'_>,
10425    visibility: &VisibilityIndex<'_>,
10426    file: &ProjectFile,
10427    source: &str,
10428    function: Node<'_>,
10429    arity: usize,
10430    receiver_resolver: Option<&ReceiverResolver<'_>>,
10431) -> Option<CppScanBinding> {
10432    debug_assert_eq!(
10433        function.kind(),
10434        "field_expression",
10435        "the member-call return binding answers only for a field-expression callee"
10436    );
10437    let receiver_resolver = receiver_resolver?;
10438    let field = function.child_by_field_name("field")?;
10439    let member_name = node_text(function_terminal_node(field), source);
10440    let receiver = function
10441        .child_by_field_name("argument")
10442        .or_else(|| function.named_child(0))?;
10443    let owners = receiver_resolver(receiver, source);
10444    let mut candidates = Vec::new();
10445    for owner in owners {
10446        let declaring_owner =
10447            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10448                EnclosingMemberOwnerResolution::Owner(owner) => owner,
10449                EnclosingMemberOwnerResolution::Missing => continue,
10450                EnclosingMemberOwnerResolution::Ambiguous => return None,
10451            };
10452        candidates.extend(
10453            visibility
10454                .visible_members_for_owner_name(file, &declaring_owner, member_name)
10455                .into_iter()
10456                .filter(|unit| {
10457                    unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
10458                })
10459                .cloned(),
10460        );
10461    }
10462    unanimous_return_binding(analyzer, visibility, file, &candidates)
10463}
10464
10465fn unanimous_return_binding(
10466    analyzer: &CppGraphSource<'_>,
10467    visibility: &VisibilityIndex<'_>,
10468    file: &ProjectFile,
10469    candidates: &[CodeUnit],
10470) -> Option<CppScanBinding> {
10471    let mut resolved_return: Option<CppScanBinding> = None;
10472    for function in candidates {
10473        let metadata = analyzer.signature_metadata(function);
10474        let return_types = if metadata.is_empty() {
10475            vec![cpp_function_return_type_text(analyzer, function)?]
10476        } else {
10477            metadata
10478                .iter()
10479                .map(|metadata| metadata.return_type_text().map(str::to_string))
10480                .collect::<Option<Vec<_>>>()?
10481        };
10482        for return_text in return_types {
10483            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10484            let name = normalize_cpp_type_name(&return_text);
10485            let binding = CppScanBinding::from_type_name(
10486                name.clone(),
10487                visibility
10488                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10489                indirection,
10490            );
10491            if let Some(existing) = resolved_return.as_ref()
10492                && (existing.indirection != binding.indirection
10493                    || match (&existing.unit, &binding.unit) {
10494                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
10495                        (None, None) => existing.type_name != binding.type_name,
10496                        (Some(_), None) | (None, Some(_)) => true,
10497                    })
10498            {
10499                return None;
10500            }
10501            resolved_return = Some(binding);
10502        }
10503    }
10504    resolved_return
10505}
10506
10507fn aliases_from_prepared_source(
10508    cpp: &dyn CppSource,
10509    token: QueryToken<'_>,
10510    file: &ProjectFile,
10511) -> Vec<CppAlias> {
10512    let Some(prepared) = cpp.prepared_syntax(token, file) else {
10513        return Vec::new();
10514    };
10515    let mut aliases = Vec::new();
10516    collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
10517    aliases
10518}
10519
10520fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
10521    walk_named_tree_preorder(root, true, |node| {
10522        match node.kind() {
10523            "alias_declaration" if alias_has_visible_file_scope(node) => {
10524                if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
10525                    out.push(alias);
10526                }
10527            }
10528            "type_definition" if alias_has_visible_file_scope(node) => {
10529                collect_typedef_aliases(node, source, out)
10530            }
10531            _ => {}
10532        }
10533        WalkControl::Continue
10534    });
10535}
10536
10537fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10538    let mut current = node.parent();
10539    while let Some(parent) = current {
10540        match parent.kind() {
10541            "translation_unit"
10542            | "namespace_definition"
10543            | "declaration_list"
10544            | "linkage_specification" => current = parent.parent(),
10545            "template_declaration" => current = parent.parent(),
10546            _ => return false,
10547        }
10548    }
10549    true
10550}
10551
10552fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
10553    let name = node
10554        .child_by_field_name("name")
10555        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
10556    let target = node
10557        .child_by_field_name("type")
10558        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
10559    Some(CppAlias {
10560        name,
10561        target,
10562        namespace: enclosing_namespace_context(node, source),
10563    })
10564}
10565
10566fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
10567    let Some(type_node) = node.child_by_field_name("type") else {
10568        return;
10569    };
10570    let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
10571        return;
10572    };
10573
10574    let mut cursor = node.walk();
10575    for child in node.named_children(&mut cursor) {
10576        if same_node(child, type_node) {
10577            continue;
10578        }
10579        if let Some(name) = extract_typedef_declarator_name(child, source) {
10580            out.push(CppAlias {
10581                name,
10582                target: target.clone(),
10583                namespace: enclosing_namespace_context(node, source),
10584            });
10585        }
10586    }
10587}
10588
10589fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
10590    match node.kind() {
10591        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
10592            normalize_reference_name(node_text(node, source))
10593        }
10594        _ => node
10595            .child_by_field_name("declarator")
10596            .or_else(|| node.child_by_field_name("name"))
10597            .or_else(|| last_named_child(node))
10598            .and_then(|child| extract_typedef_declarator_name(child, source)),
10599    }
10600}
10601
10602fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
10603    let count = node.named_child_count();
10604    if count == 0 {
10605        None
10606    } else {
10607        node.named_child(count - 1)
10608    }
10609}
10610
10611pub fn collect_include_closure(
10612    analyzer: &CppGraphSource<'_>,
10613    include_targets: &IncludeTargetIndex,
10614    file: &ProjectFile,
10615    out: &mut HashSet<ProjectFile>,
10616    cancellation: Option<&CancellationToken>,
10617) {
10618    let mut stack = vec![file.clone()];
10619    while let Some(file) = stack.pop() {
10620        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10621            break;
10622        }
10623        if !out.insert(file.clone()) {
10624            continue;
10625        }
10626        let imports = analyzer.import_statements(&file);
10627        for include in cpp_include_paths(&imports) {
10628            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10629                stack.push(target);
10630            }
10631        }
10632    }
10633}
10634
10635fn collect_visible_declarations(
10636    include_graph: &IncludeGraph,
10637    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10638    file: &ProjectFile,
10639    visited: &mut HashSet<ProjectFile>,
10640    out: &mut HashSet<CodeUnit>,
10641    cancellation: Option<&CancellationToken>,
10642) {
10643    let mut stack = vec![file.clone()];
10644    while let Some(file) = stack.pop() {
10645        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10646            break;
10647        }
10648        if !visited.insert(file.clone()) {
10649            continue;
10650        }
10651        if let Some(declarations) = declarations_by_file.get(&file) {
10652            out.extend(declarations.iter().cloned());
10653        }
10654        stack.extend(include_graph.targets(&file).iter().cloned());
10655    }
10656}
10657
10658pub fn signature_arity(signature: Option<&str>) -> usize {
10659    let Some(signature) = signature else {
10660        return 0;
10661    };
10662    let inner = signature
10663        .find('(')
10664        .and_then(|open| {
10665            signature[open + 1..]
10666                .find(')')
10667                .map(|close| &signature[open + 1..open + 1 + close])
10668        })
10669        .unwrap_or(signature)
10670        .trim();
10671    if inner.is_empty() || inner == "void" {
10672        return 0;
10673    }
10674    cpp_split_top_level_commas(inner).count()
10675}
10676
10677fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10678    let source = format!("void __bifrost_macro_parameters({replacement});");
10679    let mut parser = Parser::new();
10680    parser
10681        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10682        .ok()?;
10683    let tree = parser.parse(&source, None)?;
10684    let root = tree.root_node();
10685    if root.has_error() {
10686        return None;
10687    }
10688    let declaration = root.named_child(0)?;
10689    let declarator = declaration.child_by_field_name("declarator")?;
10690    let parameters = declarator.child_by_field_name("parameters")?;
10691    let mut required = 0;
10692    let mut total = 0;
10693    let mut repeated = false;
10694    let mut cursor = parameters.walk();
10695    for parameter in parameters.children(&mut cursor) {
10696        match parameter.kind() {
10697            "parameter_declaration" => {
10698                if parameter.child_by_field_name("declarator").is_none()
10699                    && parameter
10700                        .child_by_field_name("type")
10701                        .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
10702                {
10703                    continue;
10704                }
10705                required += 1;
10706                total += 1;
10707            }
10708            "optional_parameter_declaration" => total += 1,
10709            "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
10710                repeated = true;
10711            }
10712            _ => {}
10713        }
10714    }
10715    Some(CallableArity::new(required, total, repeated))
10716}
10717
10718pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
10719    analyzer
10720        .signature_metadata(unit)
10721        .into_iter()
10722        .find_map(|metadata| metadata.callable_arity())
10723        .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
10724}
10725
10726pub fn cpp_callable_parameter_types(
10727    analyzer: &CppGraphSource<'_>,
10728    unit: &CodeUnit,
10729) -> Option<Vec<String>> {
10730    analyzer
10731        .signature_metadata(unit)
10732        .into_iter()
10733        .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
10734        .or_else(|| unit.signature().and_then(cpp_signature_param_types))
10735}
10736
10737fn merge_compatible_callable_arities(
10738    left: CallableArity,
10739    right: CallableArity,
10740) -> Option<CallableArity> {
10741    let total = left.total();
10742    let left_repeated = left.accepts(total.saturating_add(1));
10743    let right_repeated = right.accepts(right.total().saturating_add(1));
10744    if total != right.total() || left_repeated != right_repeated {
10745        return None;
10746    }
10747    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10748    Some(CallableArity::new(required, total, left_repeated))
10749}
10750
10751fn find_include_activation(
10752    cpp: &dyn CppSource,
10753    token: QueryToken<'_>,
10754    file: &ProjectFile,
10755    prepared: &PreparedSyntaxTree,
10756    donor_source: &ProjectFile,
10757) -> Option<usize> {
10758    let include_targets = cpp.include_target_index();
10759    let mut direct_includes = Vec::new();
10760    let mut nodes = vec![prepared.tree().root_node()];
10761    // An include activates for the whole file, so only an unconditional
10762    // directive counts here.
10763    let reference = CallableReferenceContext {
10764        file,
10765        position: None,
10766    };
10767    while let Some(node) = nodes.pop() {
10768        if node.kind() == "preproc_include" {
10769            if callable_preprocessor_context_is_visible_for_reference(
10770                node,
10771                prepared.source(),
10772                &reference,
10773            ) {
10774                let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10775                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10776                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10777                        file,
10778                        &include,
10779                        include_targets,
10780                    )) {
10781                        direct_includes.push((node.end_byte(), target));
10782                    }
10783                }
10784            }
10785            continue;
10786        }
10787        push_named_children_reversed(node, &mut nodes);
10788    }
10789    direct_includes.sort_by_key(|(activation, _)| *activation);
10790    let mut known_missing = HashSet::default();
10791    direct_includes
10792        .into_iter()
10793        .find(|(_, direct)| {
10794            unconditional_include_reaches(
10795                cpp,
10796                token,
10797                include_targets,
10798                direct,
10799                donor_source,
10800                file,
10801                &mut known_missing,
10802            )
10803        })
10804        .map(|(activation, _)| activation)
10805}
10806
10807fn find_conditional_include_projection_index(
10808    cpp: &dyn CppSource,
10809    token: QueryToken<'_>,
10810    file: &ProjectFile,
10811    prepared: &PreparedSyntaxTree,
10812    on_state: &dyn Fn(),
10813) -> ConditionalIncludeProjectionIndex {
10814    let reference_is_c = reference_uses_c_semantics(cpp, file);
10815    let include_targets = cpp.include_target_index();
10816    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10817        HashMap::default();
10818    let mut pending = Vec::new();
10819    let mut nodes = vec![prepared.tree().root_node()];
10820    while let Some(node) = nodes.pop() {
10821        if node.kind() == "preproc_include" {
10822            let Some(required_guards) =
10823                include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10824            else {
10825                continue;
10826            };
10827            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10828            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10829                let Some(target) = unique_include_target(resolve_include_targets_with_index(
10830                    file,
10831                    &include,
10832                    include_targets,
10833                )) else {
10834                    continue;
10835                };
10836                pending.push((target, node.end_byte(), required_guards.clone()));
10837            }
10838            continue;
10839        }
10840        push_named_children_reversed(node, &mut nodes);
10841    }
10842
10843    // One reached file can have several distinct compatible guard paths. Each
10844    // (file, activation byte) key keeps only the inclusion-minimal guard sets:
10845    // the consumers ask existence questions whose answers are monotone in the
10846    // guard set -- a path whose requirements hold, stay stable, and stay
10847    // compatible under one environment does so under every subset as well --
10848    // so a state subsumed by an existing subset cannot witness anything its
10849    // subset does not, and inserting a smaller set evicts the supersets it
10850    // subsumes. Exact-set dedup still terminated cycles, but dense `#ifdef`
10851    // lattices (QMK's per-keyboard feature guards) enumerated the powerset of
10852    // path-union guard sets through it: the state space, the per-key linear
10853    // scans, and resident memory all grew without bound (#2365).
10854    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10855        HashMap::default();
10856    while let Some((current_file, activation_byte, path)) = pending.pop() {
10857        let guard_sets = expanded
10858            .entry((current_file.clone(), activation_byte))
10859            .or_default();
10860        // Minimality is keyed on the full guard set alone: a path's undecided
10861        // guards are the per-conditional subset of that set, so a subset path
10862        // carries a subset of them too.
10863        if guard_sets
10864            .iter()
10865            .any(|existing| existing.is_subset(&path.all))
10866        {
10867            continue;
10868        }
10869        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10870            .drain(..)
10871            .partition(|existing| path.all.is_subset(existing));
10872        *guard_sets = kept;
10873        guard_sets.push(path.all.clone());
10874        if !evicted.is_empty()
10875            && let Some(projections) = projections_by_source.get_mut(&current_file)
10876        {
10877            projections.retain(|projection| {
10878                projection.activation_byte != activation_byte
10879                    || !evicted.contains(&projection.required_guards)
10880            });
10881        }
10882        on_state();
10883
10884        // A fresh minimal set has no equal in the store: equality would have
10885        // been caught by the subset check above.
10886        projections_by_source
10887            .entry(current_file.clone())
10888            .or_default()
10889            .push(ConditionalIncludeProjection {
10890                activation_byte,
10891                required_guards: path.all.clone(),
10892                partial_guards: path.partial.clone(),
10893            });
10894
10895        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
10896            continue;
10897        };
10898        let mut nodes = vec![current_prepared.tree().root_node()];
10899        while let Some(node) = nodes.pop() {
10900            if node.kind() == "preproc_include" {
10901                let Some(include_guards) = include_directive_guard_requirements(
10902                    node,
10903                    current_prepared.source(),
10904                    reference_is_c,
10905                ) else {
10906                    continue;
10907                };
10908                let Some(reached) = path.merged(&include_guards) else {
10909                    continue;
10910                };
10911                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
10912                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10913                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
10914                        &current_file,
10915                        &include,
10916                        include_targets,
10917                    )) else {
10918                        continue;
10919                    };
10920                    pending.push((target, activation_byte, reached.clone()));
10921                }
10922                continue;
10923            }
10924            push_named_children_reversed(node, &mut nodes);
10925        }
10926    }
10927
10928    projections_by_source
10929        .into_iter()
10930        .map(|(source, mut projections)| {
10931            projections.sort_by_key(|projection| projection.activation_byte);
10932            (source, Arc::from(projections))
10933        })
10934        .collect()
10935}
10936
10937/// Decide one conditional include target without materializing every source
10938/// reached by every guard combination. A path `admission` already rejects
10939/// cannot become admissible after adding nested include guards -- both rules
10940/// only ever add requirements -- so discard it before expanding the next
10941/// header.
10942#[allow(clippy::too_many_arguments)]
10943fn find_conditional_include_projection_for_source(
10944    cpp: &dyn CppSource,
10945    token: QueryToken<'_>,
10946    file: &ProjectFile,
10947    prepared: &PreparedSyntaxTree,
10948    donor_source: &ProjectFile,
10949    admission: IncludePathAdmission,
10950    reference_guards: Option<&HashSet<PreprocessorGuard>>,
10951    reference_byte: usize,
10952    on_state: &dyn Fn(),
10953) -> bool {
10954    let Some(reference_guards) = reference_guards else {
10955        return false;
10956    };
10957    let reference_is_c = reference_uses_c_semantics(cpp, file);
10958    let include_targets = cpp.include_target_index();
10959    let mut pending = Vec::new();
10960    let mut nodes = vec![prepared.tree().root_node()];
10961    while let Some(node) = nodes.pop() {
10962        if node.kind() == "preproc_include" {
10963            let Some(required_guards) =
10964                include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10965            else {
10966                continue;
10967            };
10968            if node.end_byte() > reference_byte
10969                || !admission.admits(
10970                    &required_guards.all,
10971                    &required_guards.partial,
10972                    Some(reference_guards),
10973                )
10974            {
10975                continue;
10976            }
10977            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10978            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10979                let Some(target) = unique_include_target(resolve_include_targets_with_index(
10980                    file,
10981                    &include,
10982                    include_targets,
10983                )) else {
10984                    continue;
10985                };
10986                if &target == donor_source {
10987                    return true;
10988                }
10989                pending.push((target, required_guards.clone()));
10990            }
10991            continue;
10992        }
10993        push_named_children_reversed(node, &mut nodes);
10994    }
10995
10996    let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
10997    while let Some((current_file, path)) = pending.pop() {
10998        let guard_sets = expanded.entry(current_file.clone()).or_default();
10999        if guard_sets.contains(&path.all) {
11000            continue;
11001        }
11002        guard_sets.push(path.all.clone());
11003        on_state();
11004
11005        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
11006            continue;
11007        };
11008        let mut nodes = vec![current_prepared.tree().root_node()];
11009        while let Some(node) = nodes.pop() {
11010            if node.kind() == "preproc_include" {
11011                let Some(include_guards) = include_directive_guard_requirements(
11012                    node,
11013                    current_prepared.source(),
11014                    reference_is_c,
11015                ) else {
11016                    continue;
11017                };
11018                let Some(reached) = path.merged(&include_guards) else {
11019                    continue;
11020                };
11021                if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11022                    continue;
11023                }
11024                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
11025                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
11026                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
11027                        &current_file,
11028                        &include,
11029                        include_targets,
11030                    )) else {
11031                        continue;
11032                    };
11033                    if &target == donor_source {
11034                        return true;
11035                    }
11036                    pending.push((target, reached.clone()));
11037                }
11038                continue;
11039            }
11040            push_named_children_reversed(node, &mut nodes);
11041        }
11042    }
11043    false
11044}
11045
11046/// Whether `translation_unit`'s unconditional `#include` closure reaches
11047/// `header`, directly or through any chain of headers.
11048///
11049/// The include-closure question asked on its own, for
11050/// [`crate::identity::cpp_header_body_files_are_related`]. The walk resolves
11051/// each include the way visibility does -- to a unique target or to nothing --
11052/// so a duplicated basename relates nothing, and it is memoized per file pair
11053/// on the analyzer.
11054///
11055/// The reference position is `translation_unit` itself: the question is
11056/// whether that unit compiles the header, so that unit's own dialect and
11057/// preprocessor context govern the walk.
11058pub fn cpp_include_closure_reaches(
11059    cpp: &dyn CppSource,
11060    token: QueryToken<'_>,
11061    translation_unit: &ProjectFile,
11062    header: &ProjectFile,
11063) -> bool {
11064    unconditional_include_reaches(
11065        cpp,
11066        token,
11067        cpp.include_target_index(),
11068        translation_unit,
11069        header,
11070        translation_unit,
11071        &mut HashSet::default(),
11072    )
11073}
11074
11075fn unconditional_include_reaches(
11076    cpp: &dyn CppSource,
11077    token: QueryToken<'_>,
11078    include_targets: &IncludeTargetIndex,
11079    first: &ProjectFile,
11080    donor_source: &ProjectFile,
11081    reference_file: &ProjectFile,
11082    known_missing: &mut HashSet<ProjectFile>,
11083) -> bool {
11084    if first == donor_source {
11085        return true;
11086    }
11087    if known_missing.contains(first) {
11088        return false;
11089    }
11090    let reference_is_c = reference_file
11091        .rel_path()
11092        .extension()
11093        .and_then(|extension| extension.to_str())
11094        == Some("c");
11095    if let Some(reaches) =
11096        cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
11097    {
11098        return reaches;
11099    }
11100    let mut visited = HashSet::default();
11101    let mut files = vec![first.clone()];
11102    // Only an unconditional directive extends the include reach, so the walk
11103    // asks the question without a reference position.
11104    let reference = CallableReferenceContext {
11105        file: reference_file,
11106        position: None,
11107    };
11108    while let Some(file) = files.pop() {
11109        if file == *donor_source {
11110            cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
11111            return true;
11112        }
11113        if known_missing.contains(&file) || !visited.insert(file.clone()) {
11114            continue;
11115        }
11116        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11117            continue;
11118        };
11119        // One cursor for the whole-file walk; see `named_children_iter` (#3097).
11120        let mut cursor = prepared.tree().walk();
11121        let mut nodes = vec![prepared.tree().root_node()];
11122        while let Some(node) = nodes.pop() {
11123            if node.kind() == "preproc_include" {
11124                if callable_preprocessor_context_is_visible_for_reference(
11125                    node,
11126                    prepared.source(),
11127                    &reference,
11128                ) {
11129                    let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
11130                    for include in cpp_include_paths(std::slice::from_ref(&raw)) {
11131                        if let Some(target) = unique_include_target(
11132                            resolve_include_targets_with_index(&file, &include, include_targets),
11133                        ) {
11134                            files.push(target);
11135                        }
11136                    }
11137                }
11138                continue;
11139            }
11140            let first_pushed = nodes.len();
11141            nodes.extend(node.named_children(&mut cursor));
11142            nodes[first_pushed..].reverse();
11143        }
11144    }
11145    known_missing.extend(visited);
11146    cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
11147    false
11148}
11149
11150fn declaration_guard_requirements(
11151    analyzer: &CppGraphSource<'_>,
11152    cpp: &dyn CppSource,
11153    candidate: &CodeUnit,
11154) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11155    declaration_guard_sites(analyzer, cpp, candidate, None)
11156        .into_iter()
11157        .map(|site| (site.byte, site.guards))
11158        .collect()
11159}
11160
11161/// One declaration range's preprocessor facts, as a visible-at-a-reference
11162/// question reads them.
11163struct DeclarationGuardSite {
11164    /// The byte at which the range introduces the name.
11165    byte: usize,
11166    /// The preprocessor requirements the range's declaration stands under.
11167    guards: HashSet<PreprocessorGuard>,
11168    /// Whether the range is one branch of completed `#if`/`#else` families
11169    /// that all close before the reference. Always false without a reference
11170    /// position.
11171    completed_family_before_reference: bool,
11172}
11173
11174/// The per-range facts behind [`declaration_guard_requirements`], plus the
11175/// reference-relative fact the completed-family rule asks for.
11176fn declaration_guard_sites(
11177    analyzer: &CppGraphSource<'_>,
11178    cpp: &dyn CppSource,
11179    candidate: &CodeUnit,
11180    reference_byte: Option<usize>,
11181) -> Vec<DeclarationGuardSite> {
11182    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11183        return Vec::new();
11184    };
11185    let root = prepared.tree().root_node();
11186    analyzer
11187        .ranges(candidate)
11188        .into_iter()
11189        .filter_map(|range| {
11190            let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
11191            let guards = preprocessor_guard_environment(node, prepared.source())?;
11192            // A class name is injected into its own body at the declaration's
11193            // introduction point, not after the complete class range. Using
11194            // the start also preserves normal before/after ordering for aliases.
11195            Some(DeclarationGuardSite {
11196                byte: range.start_byte,
11197                guards,
11198                completed_family_before_reference: reference_byte.is_some_and(|reference_byte| {
11199                    declaration_follows_completed_family_before_reference(
11200                        node,
11201                        prepared.source(),
11202                        reference_byte,
11203                    )
11204                }),
11205            })
11206        })
11207        .collect()
11208}
11209
11210/// Whether every conditional enclosing `node` is a completed
11211/// `#if`/`#elif`/`#else` family that closes before `reference_byte`.
11212///
11213/// A completed family is an exhaustive case analysis: every configuration
11214/// takes exactly one of its branches. A reference sited below the whole family
11215/// is compiled in a configuration that either takes the declaration's branch,
11216/// in which case the declaration is what the name resolves to there, or takes
11217/// another branch, in which case the declaration is simply not part of that
11218/// configuration. Nothing in the reference's own position settles the family,
11219/// so the declaration is one resolution the source presents at the reference
11220/// rather than a contradiction of it (#3297).
11221///
11222/// A lone `#if` is not a case analysis: nothing in the source says any
11223/// configuration defines its macro, so a declaration under one stays
11224/// unprovable and the implication rule keeps it out. Neither does a family
11225/// whose end is not behind the reference: a reference inside the other branch
11226/// contradicts the declaration, and a reference inside the declaration's own
11227/// branch is already covered by implication.
11228fn declaration_follows_completed_family_before_reference(
11229    node: Node<'_>,
11230    source: &str,
11231    reference_byte: usize,
11232) -> bool {
11233    let mut inside_family = false;
11234    let mut ancestor = node.parent();
11235    while let Some(conditional) = ancestor {
11236        if matches!(
11237            conditional.kind(),
11238            "preproc_if" | "preproc_ifdef" | "preproc_elif"
11239        ) && !is_file_covering_include_guard(conditional, source)
11240            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11241            && preprocessor_conditional_contains_descendant(conditional, node)
11242        {
11243            let family = preprocessor_conditional_family_root(conditional);
11244            if !preprocessor_conditional_family_has_terminal_else(family)
11245                || family.end_byte() > reference_byte
11246            {
11247                return false;
11248            }
11249            inside_family = true;
11250        }
11251        ancestor = conditional.parent();
11252    }
11253    inside_family
11254}
11255
11256fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11257    analyzer
11258        .ranges(candidate)
11259        .into_iter()
11260        .map(|range| range.start_byte)
11261        .min()
11262}
11263
11264/// The macro names every configuration in `contexts` defines -- the fact set
11265/// one file's compile-database coverage proves (#2011). `None` when the
11266/// database has no entry for the file, which is different from an empty
11267/// intersection: no entry means no coverage, while an empty intersection is
11268/// covered-and-proves-nothing.
11269fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11270    let (first, rest) = contexts.split_first()?;
11271    Some(
11272        first
11273            .defined_macros
11274            .iter()
11275            .filter(|name| {
11276                rest.iter()
11277                    .all(|context| context.defined_macros.contains(*name))
11278            })
11279            .cloned()
11280            .collect(),
11281    )
11282}
11283
11284pub fn guard_requirements_hold_at_reference(
11285    required: &HashSet<PreprocessorGuard>,
11286    reference: Option<&HashSet<PreprocessorGuard>>,
11287) -> bool {
11288    reference.is_some_and(|active| {
11289        required
11290            .iter()
11291            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11292    })
11293}
11294
11295fn preprocessor_guard_holds_at_reference(
11296    required: &PreprocessorGuard,
11297    active: &HashSet<PreprocessorGuard>,
11298) -> bool {
11299    if active.contains(required) {
11300        return true;
11301    }
11302    let active_expression = BooleanGuardExpression::all(
11303        active
11304            .iter()
11305            .filter_map(PreprocessorGuard::as_boolean_expression),
11306    );
11307    required
11308        .as_boolean_expression()
11309        .is_some_and(|required| active_expression.implies(&required))
11310}
11311
11312/// Cross-file guard rule: two guard sets are compatible when neither one
11313/// contradicts the other. Use this instead of the subset test whenever the
11314/// guards come from a foreign file, which resolves its own conditionals
11315/// independently of the reference.
11316fn guards_compatible_at_reference(
11317    declaration: &HashSet<PreprocessorGuard>,
11318    reference: Option<&HashSet<PreprocessorGuard>>,
11319) -> bool {
11320    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11321}
11322
11323/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
11324/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
11325/// conditional.
11326///
11327/// Two declarations of one name that report the same chain stand in different
11328/// branches of it, so at most one of them is compiled in any configuration.
11329/// They are alternate spellings of a single declaration, not competing
11330/// declarations, and navigation must not present them as an ambiguity.
11331pub fn preprocessor_conditional_family_range(
11332    root: Node<'_>,
11333    start_byte: usize,
11334    end_byte: usize,
11335) -> Option<(usize, usize)> {
11336    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11337    let mut ancestor = Some(node);
11338    while let Some(current) = ancestor {
11339        if is_preprocessor_conditional(current)
11340            && preprocessor_conditional_contains_descendant(current, node)
11341        {
11342            let family = preprocessor_conditional_family_root(current);
11343            return Some((family.start_byte(), family.end_byte()));
11344        }
11345        ancestor = current.parent();
11346    }
11347    None
11348}
11349
11350fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
11351    let mut ancestor = node.parent();
11352    while let Some(current) = ancestor {
11353        if is_preprocessor_conditional(current)
11354            && preprocessor_conditional_contains_descendant(current, node)
11355        {
11356            let family = preprocessor_conditional_family_root(current);
11357            if preprocessor_conditional_family_has_terminal_else(family) {
11358                return Some(family);
11359            }
11360        }
11361        ancestor = current.parent();
11362    }
11363    None
11364}
11365
11366fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11367    while let Some(parent) = conditional.parent() {
11368        let is_alternative = parent
11369            .child_by_field_name("alternative")
11370            .is_some_and(|alternative| {
11371                alternative.start_byte() == conditional.start_byte()
11372                    && alternative.end_byte() == conditional.end_byte()
11373            });
11374        if !is_alternative {
11375            break;
11376        }
11377        conditional = parent;
11378    }
11379    conditional
11380}
11381
11382fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
11383    loop {
11384        let Some(alternative) = conditional.child_by_field_name("alternative") else {
11385            return false;
11386        };
11387        match alternative.kind() {
11388            "preproc_else" => return true,
11389            "preproc_elif" => conditional = alternative,
11390            _ => return false,
11391        }
11392    }
11393}
11394
11395/// The undecidable preprocessor guards an `#include` directive stands under,
11396/// or `None` when its conditional context keeps the directive out of a
11397/// translation unit of the reference's language: a false constant, a
11398/// condition this analyzer cannot read, or a `__cplusplus` branch that
11399/// language never takes.
11400///
11401/// This is [`callable_declaration_guard_requirements`]'s rule for the include
11402/// edge, and the language test is what keeps
11403/// [`IncludePathAdmission::Compatible`] honest: an empty active guard set
11404/// never implies `defined(__cplusplus)`, but it never contradicts it either,
11405/// so without this a `.c` file would reach the C++ arm of every
11406/// `#ifdef __cplusplus` / `#else` include pair.
11407fn include_directive_guard_requirements(
11408    node: Node<'_>,
11409    source: &str,
11410    reference_is_c: bool,
11411) -> Option<PreprocessorGuardEnvironment> {
11412    let required = preprocessor_guard_environment_by_family(node, source)?;
11413    let excluded_by_language = required.all.iter().any(|guard| match guard {
11414        PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11415        PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11416        _ => false,
11417    });
11418    (!excluded_by_language).then_some(required)
11419}
11420
11421pub fn preprocessor_guard_environment(
11422    node: Node<'_>,
11423    source: &str,
11424) -> Option<HashSet<PreprocessorGuard>> {
11425    preprocessor_guard_environment_by_family(node, source).map(|environment| environment.all)
11426}
11427
11428/// The preprocessor guards `node` stands under, split by whether the
11429/// conditional that contributes each one belongs to a family that covers every
11430/// configuration.
11431///
11432/// A complete `#if`/`#else` or `#if`/`#elif`/`#else` family is a case analysis
11433/// every configuration takes exactly one branch of, which is why the resolver
11434/// already reads a declaration through one (`exhaustive_guard_family_activation`,
11435/// `complementary_same_fqn_type_declarations`). A lone `#if` is not: nothing in
11436/// the source says any configuration defines that macro, so only the build can
11437/// decide it, and it stays in `partial`.
11438#[derive(Clone)]
11439struct PreprocessorGuardEnvironment {
11440    all: HashSet<PreprocessorGuard>,
11441    partial: HashSet<PreprocessorGuard>,
11442}
11443
11444impl PreprocessorGuardEnvironment {
11445    /// The environment of a path standing under both `self` and `other`, or
11446    /// `None` when the two contradict. A guard a lone `#if` contributes on
11447    /// either side stays undecided for the whole path.
11448    fn merged(&self, other: &Self) -> Option<Self> {
11449        Some(Self {
11450            all: merge_preprocessor_guards(&self.all, &other.all)?,
11451            partial: self.partial.union(&other.partial).cloned().collect(),
11452        })
11453    }
11454}
11455
11456fn preprocessor_guard_environment_by_family(
11457    node: Node<'_>,
11458    source: &str,
11459) -> Option<PreprocessorGuardEnvironment> {
11460    let mut all = HashSet::default();
11461    let mut partial = HashSet::default();
11462    let mut ancestor = node.parent();
11463    while let Some(conditional) = ancestor {
11464        if matches!(
11465            conditional.kind(),
11466            "preproc_if" | "preproc_ifdef" | "preproc_elif"
11467        ) && !is_file_covering_include_guard(conditional, source)
11468            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11469            && preprocessor_conditional_contains_descendant(conditional, node)
11470        {
11471            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11472            match guard {
11473                PreprocessorGuard::Constant(true) => {
11474                    ancestor = conditional.parent();
11475                    continue;
11476                }
11477                PreprocessorGuard::Constant(false) => return None,
11478                _ => {}
11479            }
11480            if all.contains(&guard.negated()) {
11481                return None;
11482            }
11483            if !preprocessor_conditional_family_has_terminal_else(
11484                preprocessor_conditional_family_root(conditional),
11485            ) {
11486                partial.insert(guard.clone());
11487            }
11488            all.insert(guard);
11489        }
11490        ancestor = conditional.parent();
11491    }
11492    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11493        match guard {
11494            PreprocessorGuard::Constant(true) => {}
11495            PreprocessorGuard::Constant(false) => return None,
11496            _ => {
11497                if all.contains(&guard.negated()) {
11498                    return None;
11499                }
11500                // The family of a conditional the parser split across a
11501                // statement boundary is not readable from the tree, so it
11502                // counts as undecided.
11503                partial.insert(guard.clone());
11504                all.insert(guard);
11505            }
11506        }
11507    }
11508    Some(PreprocessorGuardEnvironment { all, partial })
11509}
11510
11511fn fragmented_statement_preprocessor_guard(
11512    descendant: Node<'_>,
11513    source: &str,
11514) -> Option<PreprocessorGuard> {
11515    // A conditional that starts before `} else if (...) {` crosses the
11516    // enclosing statement's grammar boundary. tree-sitter leaves its opener
11517    // as a `preproc_if` with a missing terminator in the consequence and
11518    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
11519    // those structured nodes before restoring the guard to intervening uses.
11520    let mut ancestor = descendant.parent();
11521    while let Some(statement) = ancestor {
11522        if statement.kind() == "if_statement"
11523            && let (Some(consequence), Some(alternative)) = (
11524                statement.child_by_field_name("consequence"),
11525                statement.child_by_field_name("alternative"),
11526            )
11527            && alternative.start_byte() <= descendant.start_byte()
11528            && descendant.end_byte() <= alternative.end_byte()
11529        {
11530            let mut cursor = consequence.walk();
11531            let openers = consequence
11532                .named_children(&mut cursor)
11533                .filter(|child| {
11534                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11535                        && child
11536                            .child(child.child_count().saturating_sub(1))
11537                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11538                })
11539                .collect::<Vec<_>>();
11540            if openers.len() != 1 {
11541                ancestor = statement.parent();
11542                continue;
11543            }
11544
11545            let mut terminators = Vec::new();
11546            let mut stack = vec![alternative];
11547            while let Some(node) = stack.pop() {
11548                if node.kind() == "preproc_call"
11549                    && node.start_byte() >= descendant.end_byte()
11550                    && node
11551                        .child_by_field_name("directive")
11552                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11553                {
11554                    terminators.push(node);
11555                    continue;
11556                }
11557                push_named_children_reversed(node, &mut stack);
11558            }
11559            if terminators.len() == 1 {
11560                return simple_preprocessor_guard(openers[0], source);
11561            }
11562        }
11563        ancestor = statement.parent();
11564    }
11565    None
11566}
11567
11568fn preprocessor_guard_for_descendant(
11569    conditional: Node<'_>,
11570    descendant: Node<'_>,
11571    source: &str,
11572) -> Option<PreprocessorGuard> {
11573    let mut guard = simple_preprocessor_guard(conditional, source)?;
11574    if conditional
11575        .child_by_field_name("alternative")
11576        .is_some_and(|alternative| {
11577            alternative.start_byte() <= descendant.start_byte()
11578                && descendant.end_byte() <= alternative.end_byte()
11579        })
11580    {
11581        let alternative = conditional.child_by_field_name("alternative")?;
11582        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
11583        // descendant in any later branch must first exclude the parent branch,
11584        // then collect the nested `preproc_elif` guard from its own ancestor.
11585        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
11586            return None;
11587        }
11588        guard = guard.negated();
11589    }
11590    Some(guard)
11591}
11592
11593fn preprocessor_conditional_contains_descendant(
11594    conditional: Node<'_>,
11595    descendant: Node<'_>,
11596) -> bool {
11597    cpp_displaced_preprocessor_boundary(conditional)
11598        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11599}
11600
11601pub fn merge_preprocessor_guards(
11602    left: &HashSet<PreprocessorGuard>,
11603    right: &HashSet<PreprocessorGuard>,
11604) -> Option<HashSet<PreprocessorGuard>> {
11605    let mut merged = left.clone();
11606    for guard in right {
11607        let boolean_negation = guard
11608            .as_boolean_expression()
11609            .map(|expression| expression.negated());
11610        if merged.contains(&guard.negated())
11611            || boolean_negation.is_some_and(|negated| {
11612                merged
11613                    .iter()
11614                    .filter_map(PreprocessorGuard::as_boolean_expression)
11615                    .any(|existing| existing == negated)
11616            })
11617        {
11618            return None;
11619        }
11620        merged.insert(guard.clone());
11621    }
11622    Some(merged)
11623}
11624
11625fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11626    if conditional.kind() == "preproc_ifdef" {
11627        let name = conditional.child_by_field_name("name")?;
11628        let name = node_text(name, source).to_string();
11629        return match conditional.child(0)?.kind() {
11630            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11631            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11632            _ => None,
11633        };
11634    }
11635    let condition = conditional.child_by_field_name("condition")?;
11636    simple_preprocessor_expression_guard(condition, source).or_else(|| {
11637        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
11638            node_text(condition, source),
11639        )))
11640    })
11641}
11642
11643fn simple_preprocessor_expression_guard(
11644    expression: Node<'_>,
11645    source: &str,
11646) -> Option<PreprocessorGuard> {
11647    match expression.kind() {
11648        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
11649            node_text(expression, source).to_string(),
11650        ))),
11651        "number_literal" => match node_text(expression, source).trim() {
11652            "0" => Some(PreprocessorGuard::Constant(false)),
11653            "1" => Some(PreprocessorGuard::Constant(true)),
11654            _ => None,
11655        },
11656        "preproc_defined" => {
11657            let identifier = (0..expression.named_child_count())
11658                .filter_map(|index| expression.named_child(index))
11659                .find(|child| child.kind() == "identifier")?;
11660            Some(PreprocessorGuard::Defined(
11661                node_text(identifier, source).to_string(),
11662            ))
11663        }
11664        "unary_expression"
11665            if expression
11666                .child_by_field_name("operator")
11667                .is_some_and(|operator| operator.kind() == "!") =>
11668        {
11669            simple_preprocessor_expression_guard(
11670                expression.child_by_field_name("argument")?,
11671                source,
11672            )
11673            .map(|guard| guard.negated())
11674        }
11675        "parenthesized_expression" => (0..expression.named_child_count())
11676            .filter_map(|index| expression.named_child(index))
11677            .next()
11678            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
11679        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
11680            expression, source,
11681        ))),
11682        _ => None,
11683    }
11684}
11685
11686fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
11687    match expression.kind() {
11688        "number_literal" => match node_text(expression, source).trim() {
11689            "0" => BooleanGuardExpression::Constant(false),
11690            "1" => BooleanGuardExpression::Constant(true),
11691            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11692                expression, source,
11693            ))),
11694        },
11695        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
11696        "preproc_defined" => {
11697            let identifier = (0..expression.named_child_count())
11698                .filter_map(|index| expression.named_child(index))
11699                .find(|child| child.kind() == "identifier");
11700            identifier.map_or_else(
11701                || {
11702                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11703                        expression, source,
11704                    )))
11705                },
11706                |identifier| {
11707                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
11708                },
11709            )
11710        }
11711        "unary_expression"
11712            if expression
11713                .child_by_field_name("operator")
11714                .is_some_and(|operator| operator.kind() == "!") =>
11715        {
11716            expression.child_by_field_name("argument").map_or_else(
11717                || {
11718                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11719                        expression, source,
11720                    )))
11721                },
11722                |argument| boolean_preprocessor_expression(argument, source).negated(),
11723            )
11724        }
11725        "parenthesized_expression" => (0..expression.named_child_count())
11726            .filter_map(|index| expression.named_child(index))
11727            .next()
11728            .map_or_else(
11729                || {
11730                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11731                        expression, source,
11732                    )))
11733                },
11734                |child| boolean_preprocessor_expression(child, source),
11735            ),
11736        "binary_expression" => {
11737            let operands = || {
11738                Some((
11739                    boolean_preprocessor_expression(
11740                        expression.child_by_field_name("left")?,
11741                        source,
11742                    ),
11743                    boolean_preprocessor_expression(
11744                        expression.child_by_field_name("right")?,
11745                        source,
11746                    ),
11747                ))
11748            };
11749            match expression
11750                .child_by_field_name("operator")
11751                .map(|operator| operator.kind())
11752            {
11753                Some("&&") => operands().map_or_else(
11754                    || {
11755                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11756                            expression, source,
11757                        )))
11758                    },
11759                    |(left, right)| BooleanGuardExpression::all([left, right]),
11760                ),
11761                Some("||") => operands().map_or_else(
11762                    || {
11763                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11764                            expression, source,
11765                        )))
11766                    },
11767                    |(left, right)| BooleanGuardExpression::any([left, right]),
11768                ),
11769                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11770                    expression, source,
11771                ))),
11772            }
11773        }
11774        _ => {
11775            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
11776        }
11777    }
11778}
11779
11780fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
11781    if targets.len() == 1 {
11782        targets.pop()
11783    } else {
11784        None
11785    }
11786}
11787
11788/// The declaration nodes of `candidate` in `prepared` that stand at a scope a
11789/// later reference can name.
11790///
11791/// A declaration inside a real function body, lambda, or nested block is block
11792/// local and is dropped. A declaration inside a parser-recovery wrapper that
11793/// merely looks callable -- an export macro between `class` and its name, or a
11794/// namespace-opening macro token before `namespace x {` -- keeps class or
11795/// namespace scope and is kept.
11796fn nameable_callable_declaration_nodes<'tree>(
11797    analyzer: &CppGraphSource<'_>,
11798    prepared: &'tree PreparedSyntaxTree,
11799    candidate: &CodeUnit,
11800) -> Vec<Node<'tree>> {
11801    callable_declaration_nodes(analyzer, prepared, candidate)
11802        .into_iter()
11803        .filter(|declaration| {
11804            let mut ancestor = declaration.parent();
11805            while let Some(node) = ancestor {
11806                if node.kind() == "function_definition"
11807                    && is_recovered_declaration_scope_container(node, prepared.source())
11808                {
11809                    ancestor = node.parent();
11810                    continue;
11811                }
11812                if node.kind() == "compound_statement"
11813                    && node.parent().is_some_and(|parent| {
11814                        is_recovered_declaration_scope_container(parent, prepared.source())
11815                    })
11816                {
11817                    ancestor = node.parent().and_then(|parent| parent.parent());
11818                    continue;
11819                }
11820                if matches!(
11821                    node.kind(),
11822                    "compound_statement" | "function_definition" | "lambda_expression"
11823                ) {
11824                    return false;
11825                }
11826                ancestor = node.parent();
11827            }
11828            true
11829        })
11830        .collect()
11831}
11832
11833fn callable_declaration_nodes<'tree>(
11834    analyzer: &CppGraphSource<'_>,
11835    prepared: &'tree PreparedSyntaxTree,
11836    candidate: &CodeUnit,
11837) -> Vec<Node<'tree>> {
11838    let root = prepared.tree().root_node();
11839    analyzer
11840        .ranges(candidate)
11841        .into_iter()
11842        .filter_map(|range| {
11843            let mut declaration =
11844                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
11845            // A declaration an attribute-like macro invocation swallowed lives
11846            // inside the `ERROR` the parser left, not inside a `declaration`
11847            // node, so that envelope is where the climb stops (#2552).
11848            //
11849            // The climb can also run out of ancestors. A collapse that takes a
11850            // whole file with it -- whisper.cpp's `include/whisper.h` from its
11851            // first `WHISPER_DEPRECATED(` to the end (#3094) -- leaves no
11852            // envelope around most of what a region reparse recovers there, and
11853            // the recovered declaration's own `;` can fall outside the envelope
11854            // it does leave, so the range spans the container rather than one
11855            // node. Either way the declaration stands at the scope the climb
11856            // ended in, which is what the callers ask these nodes about.
11857            while !matches!(
11858                declaration.kind(),
11859                "declaration" | "field_declaration" | "function_definition"
11860            ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
11861                declaration,
11862                prepared.source(),
11863            ) {
11864                let Some(parent) = declaration.parent() else {
11865                    break;
11866                };
11867                declaration = parent;
11868            }
11869            Some(declaration)
11870        })
11871        .collect()
11872}
11873
11874fn real_function_definition_ancestor<'tree>(
11875    node: Node<'tree>,
11876    source: &str,
11877) -> Option<Node<'tree>> {
11878    let mut ancestor = node.parent();
11879    while let Some(node) = ancestor {
11880        if node.kind() == "function_definition"
11881            && !is_recovered_declaration_scope_container(node, source)
11882        {
11883            return Some(node);
11884        }
11885        ancestor = node.parent();
11886    }
11887    None
11888}
11889
11890fn callable_declaration_activation_in_file(
11891    analyzer: &CppGraphSource<'_>,
11892    prepared: &PreparedSyntaxTree,
11893    candidate: &CodeUnit,
11894    reference: &CallableReferenceContext<'_>,
11895) -> Option<usize> {
11896    nameable_callable_declaration_nodes(analyzer, prepared, candidate)
11897        .into_iter()
11898        .filter(|declaration| {
11899            callable_preprocessor_context_is_visible_for_reference(
11900                *declaration,
11901                prepared.source(),
11902                reference,
11903            )
11904        })
11905        .map(callable_declaration_activation_byte)
11906        .min()
11907}
11908
11909/// C and C++ activate a declared name at the end of its declarator, not at the
11910/// end of the whole declaration. A function definition ends at the closing
11911/// brace of its body, so the declaration end byte would hide the function from
11912/// its own body and make self recursion unresolvable without a prototype.
11913fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
11914    if declaration.kind() != "function_definition" {
11915        return declaration.end_byte();
11916    }
11917    declaration
11918        .child_by_field_name("declarator")
11919        .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
11920}
11921
11922/// The reference side of a callable visibility question.
11923///
11924/// An include-graph walk and a whole-file arity activation ask the question
11925/// without one reference position, so they carry no `position` and therefore no
11926/// guard environment.
11927struct CallableReferenceContext<'a> {
11928    file: &'a ProjectFile,
11929    position: Option<CallableReferencePosition<'a>>,
11930}
11931
11932/// One reference position plus its preprocessor guard environment. The
11933/// environment is computed on demand because most declarations carry no
11934/// non-trivial guard.
11935struct CallableReferencePosition<'a> {
11936    prepared: &'a PreparedSyntaxTree,
11937    byte: usize,
11938    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
11939}
11940
11941impl CallableReferenceContext<'_> {
11942    fn is_c(&self) -> bool {
11943        self.file
11944            .rel_path()
11945            .extension()
11946            .and_then(|extension| extension.to_str())
11947            == Some("c")
11948    }
11949
11950    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
11951        let position = self.position.as_ref()?;
11952        position
11953            .guards
11954            .get_or_init(|| {
11955                position
11956                    .prepared
11957                    .tree()
11958                    .root_node()
11959                    .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
11960                    .and_then(|node| {
11961                        preprocessor_guard_environment(node, position.prepared.source())
11962                    })
11963            })
11964            .as_ref()
11965    }
11966}
11967
11968/// The undecidable preprocessor guards `node` stands under, or `None` when its
11969/// conditional context excludes it from `reference`'s translation unit outright:
11970/// a false constant, a condition this analyzer cannot read, or a `__cplusplus`
11971/// branch the reference's language never takes.
11972///
11973/// Collecting one guard per enclosing conditional makes the whole walk a
11974/// conjunction of the declaration's requirements. Which comparison those
11975/// requirements then face depends on where the declaration lives: a same-file
11976/// declaration shares the reference's configuration, so the reference's active
11977/// guards must imply them, while a foreign file resolves its own conditionals
11978/// and only has to stay compatible with the reference.
11979fn callable_declaration_guard_requirements(
11980    node: Node<'_>,
11981    source: &str,
11982    reference: &CallableReferenceContext<'_>,
11983) -> Option<HashSet<PreprocessorGuard>> {
11984    let reference_is_c = reference.is_c();
11985    let mut required = HashSet::default();
11986    let mut ancestor = node.parent();
11987    while let Some(conditional) = ancestor {
11988        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11989            && !is_file_covering_include_guard(conditional, source)
11990            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11991            && preprocessor_conditional_contains_descendant(conditional, node)
11992        {
11993            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11994            match guard {
11995                PreprocessorGuard::Constant(true) => {}
11996                PreprocessorGuard::Constant(false) => return None,
11997                PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
11998                    if reference_is_c {
11999                        return None;
12000                    }
12001                }
12002                PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
12003                    if !reference_is_c {
12004                        return None;
12005                    }
12006                }
12007                guard => {
12008                    required.insert(guard);
12009                }
12010            }
12011        }
12012        ancestor = conditional.parent();
12013    }
12014    Some(required)
12015}
12016
12017/// Whether a declaration in the reference's own file is co-active with the
12018/// reference: one translation unit resolves every conditional the same way, so
12019/// the reference's active guards must imply the declaration's requirements.
12020///
12021/// The exception is a declaration in one branch of a completed `#if`/`#else`
12022/// family the reference follows: the reference's own position settles nothing
12023/// about that family, so a compatible branch is one resolution the source
12024/// presents at the reference (#3297), exactly as
12025/// [`VisibilityIndex::external_type_candidate_visible_in_context`] reads a type
12026/// declaration there.
12027fn callable_preprocessor_context_is_visible_for_reference(
12028    node: Node<'_>,
12029    source: &str,
12030    reference: &CallableReferenceContext<'_>,
12031) -> bool {
12032    let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12033        return false;
12034    };
12035    if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12036        return true;
12037    }
12038    reference.position.as_ref().is_some_and(|position| {
12039        guards_compatible_at_reference(&required, reference.guards())
12040            && declaration_follows_completed_family_before_reference(node, source, position.byte)
12041    })
12042}
12043
12044fn flattened_macro_namespace_declaration_matches(
12045    analyzer: &CppGraphSource<'_>,
12046    cpp: &dyn CppSource,
12047    reference_file: &ProjectFile,
12048    visible_declaration: &CodeUnit,
12049    qualified_candidate: &CodeUnit,
12050    reference_byte: usize,
12051) -> bool {
12052    // Namespace-opening macros can leave tree-sitter unable to retain the
12053    // namespace owner after a later recovery point. In that shape the forward
12054    // declaration is indexed at translation-unit scope, while the definition
12055    // still has its qualified owner. Require all surviving structural evidence
12056    // before treating the declaration as activation for that definition.
12057    if visible_declaration.kind() != qualified_candidate.kind()
12058        || visible_declaration.identifier() != qualified_candidate.identifier()
12059        || visible_declaration.signature() != qualified_candidate.signature()
12060        || !visible_declaration.package_name().is_empty()
12061        || qualified_candidate.package_name().is_empty()
12062    {
12063        return false;
12064    }
12065
12066    let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
12067        return false;
12068    };
12069    let root = prepared.tree().root_node();
12070    let closing_brace_limit = if visible_declaration.source() == reference_file {
12071        reference_byte
12072    } else {
12073        usize::MAX
12074    };
12075
12076    analyzer
12077        .ranges(visible_declaration)
12078        .into_iter()
12079        .any(|range| {
12080            let Some(mut declaration) =
12081                root.descendant_for_byte_range(range.start_byte, range.end_byte)
12082            else {
12083                return false;
12084            };
12085            while !matches!(
12086                declaration.kind(),
12087                "declaration" | "field_declaration" | "function_definition"
12088            ) {
12089                let Some(parent) = declaration.parent() else {
12090                    return false;
12091                };
12092                declaration = parent;
12093            }
12094            if declaration
12095                .parent()
12096                .is_none_or(|parent| parent.kind() != "translation_unit")
12097                || !macro_displaced_cpp_return_type(declaration, prepared.source())
12098            {
12099                return false;
12100            }
12101
12102            let mut cursor = root.walk();
12103            root.named_children(&mut cursor).any(|sibling| {
12104                sibling.start_byte() >= declaration.end_byte()
12105                    && sibling.start_byte() < closing_brace_limit
12106                    && direct_unmatched_closing_brace(sibling)
12107            })
12108        })
12109}
12110
12111fn flattened_macro_namespace_components(
12112    declaration: Node<'_>,
12113    source: &str,
12114) -> Option<Vec<String>> {
12115    flattened_macro_function_namespace_components(declaration, source)
12116        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12117}
12118
12119fn flattened_macro_function_namespace_components(
12120    declaration: Node<'_>,
12121    source: &str,
12122) -> Option<Vec<String>> {
12123    let body = declaration
12124        .parent()
12125        .filter(|parent| parent.kind() == "compound_statement")?;
12126    let function = body.parent()?;
12127    if function.child_by_field_name("body") != Some(body) {
12128        return None;
12129    }
12130    let namespace_name = recovered_macro_namespace_name(function, source)?;
12131    let mut components = enclosing_namespace_components(declaration, source)?;
12132    components.push(namespace_name);
12133    Some(components)
12134}
12135
12136/// The namespace name a namespace-opening macro token displaced into a
12137/// synthetic `function_definition`, or `None` when `function` is not that
12138/// recovery shape.
12139///
12140/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
12141/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
12142/// the macro token, whose declarator is the namespace name behind an `ERROR`
12143/// holding the `namespace` keyword, and whose body spans the whole namespace
12144/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
12145/// artifact from a real function definition.
12146fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
12147    if function.kind() != "function_definition" || !function.has_error() {
12148        return None;
12149    }
12150    let body = function
12151        .child_by_field_name("body")
12152        .filter(|body| body.kind() == "compound_statement")?;
12153    let mut cursor = function.walk();
12154    let prefix = function
12155        .named_children(&mut cursor)
12156        .take_while(|child| child.start_byte() < body.start_byte())
12157        .filter(|child| child.kind() != "comment")
12158        .collect::<Vec<_>>();
12159    let begin_index = prefix.iter().rposition(|child| {
12160        flattened_macro_sentinel_name(*child, source)
12161            .is_some_and(|name| is_namespace_begin_sentinel(&name))
12162    })?;
12163    let mut identifiers = Vec::new();
12164    let mut stack = prefix[begin_index + 1..]
12165        .iter()
12166        .rev()
12167        .copied()
12168        .collect::<Vec<_>>();
12169    while let Some(current) = stack.pop() {
12170        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
12171            identifiers.push(identifier);
12172            continue;
12173        }
12174        let mut cursor = current.walk();
12175        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
12176        stack.extend(children.into_iter().rev());
12177    }
12178    let [keyword, namespace_name] = identifiers.as_slice() else {
12179        return None;
12180    };
12181    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
12182    {
12183        return None;
12184    }
12185    let mut next = function.next_named_sibling();
12186    let next = loop {
12187        let candidate = next?;
12188        next = candidate.next_named_sibling();
12189        if candidate.kind() != "comment" {
12190            break candidate;
12191        }
12192    };
12193    flattened_macro_sentinel_name(next, source)
12194        .is_some_and(|name| is_namespace_end_sentinel(&name))
12195        .then(|| namespace_name.clone())
12196}
12197
12198/// A `function_definition` that exists only because tree-sitter recovered a
12199/// macro-decorated class head or a namespace-opening macro token. A declaration
12200/// in such a body keeps class or namespace scope, so a scope walk must step over
12201/// the wrapper instead of treating the declaration as block local.
12202fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
12203    crate::declarations::is_recovered_exported_class_container(node, source)
12204        || crate::declarations::is_recovered_fragmented_partial_specialization_container(
12205            node, source,
12206        )
12207        || recovered_macro_namespace_name(node, source).is_some()
12208}
12209
12210fn flattened_macro_error_namespace_components(
12211    declaration: Node<'_>,
12212    source: &str,
12213) -> Option<Vec<String>> {
12214    let parent = declaration
12215        .parent()
12216        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
12217    let mut cursor = parent.walk();
12218    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
12219    let declaration_index = siblings
12220        .iter()
12221        .position(|candidate| same_node(*candidate, declaration))?;
12222    let begin_index = (0..declaration_index).rev().find(|index| {
12223        flattened_macro_sentinel_name(siblings[*index], source)
12224            .is_some_and(|name| is_namespace_begin_sentinel(&name))
12225    })?;
12226
12227    let significant = siblings[begin_index + 1..declaration_index]
12228        .iter()
12229        .copied()
12230        .filter(|node| node.kind() != "comment")
12231        .collect::<Vec<_>>();
12232    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
12233        return None;
12234    };
12235    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
12236        return None;
12237    }
12238    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
12239    if significant[2..].iter().any(|node| {
12240        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
12241            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
12242        })
12243    }) {
12244        return None;
12245    }
12246
12247    let mut saw_namespace_close = false;
12248    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
12249        if sibling.kind() == "comment" {
12250            continue;
12251        }
12252        if !saw_namespace_close {
12253            if direct_unmatched_closing_brace(sibling) {
12254                saw_namespace_close = true;
12255                continue;
12256            }
12257            if flattened_macro_sentinel_name(sibling, source).is_some() {
12258                return None;
12259            }
12260            continue;
12261        }
12262        if !flattened_macro_sentinel_name(sibling, source)
12263            .is_some_and(|name| is_namespace_end_sentinel(&name))
12264        {
12265            return None;
12266        }
12267        let mut components = enclosing_namespace_components(declaration, source)?;
12268        components.push(namespace_name);
12269        return Some(components);
12270    }
12271    None
12272}
12273
12274fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12275    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
12276    // an `expression_statement` with a missing semicolon; inside a namespace
12277    // body the same token stays a bare `type_identifier`.
12278    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12279        node.named_child(0)?
12280    } else {
12281        node
12282    };
12283    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
12284        node.child_by_field_name("type")
12285            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
12286    })?;
12287    (cpp_export_macro_token(&candidate)
12288        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
12289    .then_some(candidate)
12290}
12291
12292/// Namespace-opening macros are spelled both ways in the wild:
12293/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
12294fn is_namespace_begin_sentinel(name: &str) -> bool {
12295    name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
12296}
12297
12298fn is_namespace_end_sentinel(name: &str) -> bool {
12299    name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
12300}
12301
12302fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12303    if node.kind() != "ERROR" || node.named_child_count() != 1 {
12304        return None;
12305    }
12306    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12307    (!cpp_export_macro_token(&name)).then_some(name)
12308}
12309
12310fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12311    if !matches!(
12312        node.kind(),
12313        "identifier" | "namespace_identifier" | "type_identifier"
12314    ) {
12315        return None;
12316    }
12317    let name = normalize_cpp_whitespace(node_text(node, source));
12318    (!name.is_empty()).then_some(name)
12319}
12320
12321fn guard_requirement_sets_match(
12322    left: &[(usize, HashSet<PreprocessorGuard>)],
12323    right: &[(usize, HashSet<PreprocessorGuard>)],
12324) -> bool {
12325    left.len() == right.len()
12326        && left.iter().all(|(_, left_guards)| {
12327            right
12328                .iter()
12329                .any(|(_, right_guards)| left_guards == right_guards)
12330        })
12331        && right.iter().all(|(_, right_guards)| {
12332            left.iter()
12333                .any(|(_, left_guards)| right_guards == left_guards)
12334        })
12335}
12336
12337fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
12338    let Some(type_node) = declaration.child_by_field_name("type") else {
12339        return false;
12340    };
12341    let type_name = normalize_cpp_whitespace(node_text(type_node, source));
12342    !type_name.is_empty()
12343        && type_name
12344            .chars()
12345            .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
12346        && (0..declaration.named_child_count()).any(|index| {
12347            declaration
12348                .named_child(index)
12349                .is_some_and(|child| child.kind() == "ERROR")
12350        })
12351}
12352
12353fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12354    node.kind() == "ERROR"
12355        && (0..node.child_count())
12356            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12357}
12358
12359pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12360    let mut ancestor = node.parent();
12361    while let Some(parent) = ancestor {
12362        if is_preprocessor_conditional(parent)
12363            && !is_file_covering_include_guard(parent, source)
12364            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12365        {
12366            return false;
12367        }
12368        ancestor = parent.parent();
12369    }
12370    true
12371}
12372
12373fn is_split_cpp_language_linkage_wrapper(
12374    conditional: Node<'_>,
12375    descendant: Node<'_>,
12376    source: &str,
12377) -> bool {
12378    if conditional.child_by_field_name("alternative").is_some()
12379        || !matches!(
12380            simple_preprocessor_guard(conditional, source),
12381            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12382        )
12383    {
12384        return false;
12385    }
12386    let mut current = descendant.parent();
12387    let linkage = loop {
12388        let Some(node) = current else {
12389            return false;
12390        };
12391        if node == conditional {
12392            return false;
12393        }
12394        if node.kind() == "linkage_specification" {
12395            break node;
12396        }
12397        current = node.parent();
12398    };
12399    if linkage
12400        .child_by_field_name("value")
12401        .is_none_or(|value| node_text(value, source) != "\"C\"")
12402    {
12403        return false;
12404    }
12405    let Some(body) = linkage.child_by_field_name("body") else {
12406        return false;
12407    };
12408    let closes_opening_branch = (0..body.named_child_count())
12409        .filter_map(|index| body.named_child(index))
12410        .take_while(|child| child.end_byte() <= descendant.start_byte())
12411        .any(|child| {
12412            child.kind() == "preproc_call"
12413                && child
12414                    .child_by_field_name("directive")
12415                    .is_some_and(|directive| node_text(directive, source) == "#endif")
12416        });
12417    let reopens_for_closing_brace = (0..body.named_child_count())
12418        .filter_map(|index| body.named_child(index))
12419        .skip_while(|child| child.start_byte() < descendant.end_byte())
12420        .any(|child| {
12421            matches!(
12422                simple_preprocessor_guard(child, source),
12423                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12424            ) && (0..child.child_count()).any(|index| {
12425                child
12426                    .child(index)
12427                    .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
12428            })
12429        });
12430    closes_opening_branch && reopens_for_closing_brace
12431}
12432
12433/// The argument list a call-shaped node supplies: `f(args)`, `new T(args)`,
12434/// `T{args}` and the member initializer `: field(args)`, whose grammar gives its
12435/// argument list no field name.
12436pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12437    node.child_by_field_name("arguments")
12438        .or_else(|| node.child_by_field_name("parameters"))
12439        .or_else(|| node.child_by_field_name("value"))
12440        .or_else(|| first_named_child_of_kind(node, "argument_list"))
12441        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12442}
12443
12444pub fn call_arity(node: Node<'_>) -> usize {
12445    call_arguments_node(node)
12446        .map(|args| argument_children(args).count())
12447        .unwrap_or(0)
12448}
12449
12450pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12451    let recovered_block_arguments = recovered_block_literal_arguments(node);
12452    (0..node.child_count())
12453        .filter_map(move |index| node.child(index))
12454        .filter(|child| child.is_named() && !child.is_extra())
12455        .flat_map(move |child| {
12456            if let Some((raw, left, right)) = recovered_block_arguments
12457                && child == raw
12458            {
12459                [Some(left), Some(right)]
12460            } else {
12461                [Some(child), None]
12462            }
12463        })
12464        .flatten()
12465}
12466
12467/// Recover the two ordinary C values that the C++ grammar folds into one
12468/// `new_expression` for `callee(new, trailing)`.
12469///
12470/// The malformed node has an exact grammar-owned shape: the anonymous `new`
12471/// token, an extra `ERROR` containing only the comma token, and the trailing
12472/// value in the `type` field.  The caller supplies the compilation-dialect
12473/// proof from [`reference_uses_c_semantics`]; a C++ source therefore never
12474/// reinterprets a real new-expression through this path.
12475pub fn recovered_c_new_expression_arguments(
12476    node: Node<'_>,
12477    uses_c_semantics: bool,
12478) -> Option<[Node<'_>; 2]> {
12479    if !uses_c_semantics || node.kind() != "new_expression" {
12480        return None;
12481    }
12482    let parent = node.parent()?;
12483    if parent.kind() != "argument_list" {
12484        return None;
12485    }
12486    let keyword = node.child(0)?;
12487    let error = node.child(1)?;
12488    let trailing = node.child(2)?;
12489    if node.child(3).is_some()
12490        || keyword.kind() != "new"
12491        || keyword.is_named()
12492        || keyword.child_count() != 0
12493        || error.kind() != "ERROR"
12494        || !error.is_extra()
12495        || error.child_count() != 1
12496        || error.child(0).is_none_or(|comma| comma.kind() != ",")
12497        || node.child_by_field_name("type") != Some(trailing)
12498        || trailing.kind() != "type_identifier"
12499    {
12500        return None;
12501    }
12502    Some([keyword, trailing])
12503}
12504
12505/// The recovered C value covering one focused source range, starting from any
12506/// node within the malformed new-expression.
12507pub fn recovered_c_new_expression_argument_at(
12508    mut node: Node<'_>,
12509    start_byte: usize,
12510    end_byte: usize,
12511    uses_c_semantics: bool,
12512) -> Option<Node<'_>> {
12513    // The recovery only exists for C sources, and the climb below reaches the
12514    // root before it can answer no. `Node::parent` re-descends from the root on
12515    // every step, so leaving the check inside the loop made every scanned node
12516    // of a C++ file pay a full quadratic ancestor walk for an answer settled by
12517    // the file's language (#3097).
12518    if !uses_c_semantics {
12519        return None;
12520    }
12521    loop {
12522        if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12523            return arguments.into_iter().find(|argument| {
12524                argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12525            });
12526        }
12527        node = node.parent()?;
12528    }
12529}
12530
12531fn recovered_c_keyword_argument_count(
12532    file: &ProjectFile,
12533    call: Node<'_>,
12534    arguments: Node<'_>,
12535    source: &str,
12536) -> usize {
12537    // A C identifier that is a C++ keyword can be displaced twice by the C++
12538    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
12539    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
12540    // the enclosing C function before restoring the otherwise dropped slot.
12541    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12542        return 0;
12543    }
12544    let mut ancestor = Some(call);
12545    let function = loop {
12546        let Some(current) = ancestor else {
12547            return 0;
12548        };
12549        if current.kind() == "function_definition" {
12550            break current;
12551        }
12552        ancestor = current.parent();
12553    };
12554    let Some(parameters) = function
12555        .child_by_field_name("declarator")
12556        .and_then(|declarator| declarator.child_by_field_name("parameters"))
12557    else {
12558        return 0;
12559    };
12560    let displaced_parameter_keywords = (0..parameters.child_count())
12561        .filter_map(|index| parameters.child(index))
12562        .filter(|error| error.kind() == "ERROR")
12563        .filter_map(|error| {
12564            let parameter = error.prev_named_sibling()?;
12565            if parameter.kind() != "parameter_declaration"
12566                || parameter.end_byte() != error.start_byte()
12567                || extract_variable_name(parameter, source).is_some()
12568            {
12569                return None;
12570            }
12571            let mut children = (0..error.child_count())
12572                .filter_map(|index| error.child(index))
12573                .filter(|child| !child.is_extra() && !child.is_missing());
12574            let keyword = children.next()?;
12575            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12576                .then_some(keyword)
12577        })
12578        .collect::<Vec<_>>();
12579    if displaced_parameter_keywords.is_empty() {
12580        return 0;
12581    }
12582
12583    (0..arguments.child_count())
12584        .filter_map(|index| arguments.child(index))
12585        .filter(|error| error.kind() == "ERROR" && error.is_extra())
12586        .filter(|error| {
12587            let mut children = (0..error.child_count())
12588                .filter_map(|index| error.child(index))
12589                .filter(|child| !child.is_extra() && !child.is_missing());
12590            let Some(comma) = children.next() else {
12591                return false;
12592            };
12593            let Some(keyword) = children.next() else {
12594                return false;
12595            };
12596            children.next().is_none()
12597                && comma.kind() == ","
12598                && !keyword.is_named()
12599                && keyword.child_count() == 0
12600                && displaced_parameter_keywords
12601                    .iter()
12602                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
12603        })
12604        .count()
12605}
12606
12607fn recovered_block_literal_arguments<'tree>(
12608    arguments: Node<'tree>,
12609) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
12610    if arguments.kind() != "argument_list" {
12611        return None;
12612    }
12613    let mut raw_arguments = (0..arguments.child_count())
12614        .filter_map(|index| arguments.child(index))
12615        .filter(|child| child.is_named() && !child.is_extra());
12616    let raw = raw_arguments.next()?;
12617    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
12618        return None;
12619    }
12620
12621    let left = raw.child_by_field_name("left")?;
12622    if left.is_missing() || left.start_byte() == left.end_byte() {
12623        return None;
12624    }
12625    let right = raw.child_by_field_name("right")?;
12626    if right.kind() != "compound_literal_expression"
12627        || right.is_missing()
12628        || right
12629            .child_by_field_name("type")
12630            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
12631        || right
12632            .child_by_field_name("value")
12633            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
12634    {
12635        return None;
12636    }
12637    let has_intervening_error = (0..raw.child_count())
12638        .filter_map(|index| raw.child(index))
12639        .any(|child| {
12640            child.kind() == "ERROR"
12641                && !child.is_missing()
12642                && child.start_byte() >= left.end_byte()
12643                && child.end_byte() <= right.start_byte()
12644        });
12645    has_intervening_error.then_some((raw, left, right))
12646}
12647
12648pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
12649    match node.kind() {
12650        "new_expression" => node
12651            .child_by_field_name("type")
12652            .or_else(|| node.named_child(0)),
12653        "compound_literal_expression" => node.child_by_field_name("type"),
12654        "call_expression" => node.child_by_field_name("function"),
12655        _ => None,
12656    }
12657}
12658
12659/// The structured type named by a C-style cast expression.
12660///
12661/// Tree-sitter wraps the actual type syntax in a `type_descriptor`.  Return its
12662/// structured `type` field so resolution retains qualified and nested syntax
12663/// without reparsing source text.
12664pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
12665    if node.kind() != "cast_expression" {
12666        return None;
12667    }
12668    let descriptor = node.child_by_field_name("type")?;
12669    if descriptor.kind() == "type_descriptor" {
12670        descriptor.child_by_field_name("type")
12671    } else {
12672        Some(descriptor)
12673    }
12674}
12675
12676/// The expression a field access reads its member from.
12677///
12678/// That is normally the node in the `argument` field. A preprocessor
12679/// conditional inside a call's argument list defeats the grammar: tree-sitter
12680/// ends the argument list at the `#if` line, then reads the text that follows
12681/// `#endif` as a field access on the whole call expression and leaves the real
12682/// receiver in an `ERROR` node between the `argument` child and the operator.
12683/// mbedtls writes exactly that at `library/ssl_tls.c:4999`, where
12684/// `ssl->session->encrypt_then_mac` is one conditionally compiled call argument
12685/// (#2982).
12686///
12687/// `.` and `->` read whatever ends immediately before them in either tree, so
12688/// answer from the operator's position instead of trusting the `argument`
12689/// field, and unwrap the single expression a displaced `ERROR` holds.
12690pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
12691    debug_assert_eq!(field.kind(), "field_expression");
12692    let operator = field.child_by_field_name("operator")?;
12693    let mut cursor = field.walk();
12694    let receiver = field
12695        .named_children(&mut cursor)
12696        .filter(|child| child.end_byte() <= operator.start_byte())
12697        .last()?;
12698    if receiver.kind() != "ERROR" {
12699        return Some(receiver);
12700    }
12701    (receiver.named_child_count() == 1)
12702        .then(|| receiver.named_child(0))
12703        .flatten()
12704}
12705
12706pub fn field_initializer_constructs_target(
12707    node: Node<'_>,
12708    ctx: &ScanCtx<'_>,
12709    owner: &CodeUnit,
12710) -> bool {
12711    // A qualified name in a constructor initializer denotes a base
12712    // subobject constructor (`namespace::Base(args)`), not a member field.  The
12713    // field-initializer grammar exposes the qualified name as one structured
12714    // `qualified_identifier`; resolve its owner through the same lexical type
12715    // machinery used for ordinary C++ type references before considering the
12716    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
12717    // qualified non-constructor member, and an unresolved owner out of the
12718    // target constructor's inverse usage set.
12719    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
12720        return qualified_base_initializer_constructs_target(node, ctx, owner);
12721    }
12722    let Some(name) = node
12723        .child_by_field_name("name")
12724        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
12725        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
12726    else {
12727        return false;
12728    };
12729    let field_name = node_text(name, ctx.source);
12730    ctx.visibility
12731        .visible_identifier_candidates(ctx.file, field_name)
12732        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
12733        .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
12734}
12735
12736/// Whether a member initializer for `unit` runs a constructor of the type the
12737/// member declares.
12738///
12739/// Only a member that holds the type by value does. `Owner* p` initialized as
12740/// `p(nullptr)` binds a pointer and `Owner& r` as `r(other)` binds a
12741/// reference; neither runs an `Owner` constructor, even though both declare
12742/// `Owner` as the receiver type a member access through them resolves (#3286).
12743/// A member whose declaration the index cannot decode keeps the by-value
12744/// reading, which is what the declared-type match already assumed.
12745fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
12746    ctx.visibility
12747        .field_declared_type_fact(&ctx.analyzer, unit)
12748        .is_none_or(|fact| !fact.binds_indirectly)
12749}
12750
12751fn qualified_base_initializer_constructs_target(
12752    node: Node<'_>,
12753    ctx: &ScanCtx<'_>,
12754    owner: &CodeUnit,
12755) -> bool {
12756    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
12757        return false;
12758    };
12759    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
12760        return false;
12761    };
12762    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
12763        return false;
12764    };
12765    let resolves_target = |components: &[String]| {
12766        matches!(
12767            ctx.visibility.resolve_type_components_lexically_for_target(
12768                &ctx.analyzer,
12769                ctx.file,
12770                components,
12771                is_globally_qualified_cpp_name(qualified),
12772                &lexical_scope,
12773                owner,
12774            ),
12775            LexicalTypeResolution::Resolved { unit, .. }
12776                if same_visible_symbol(&unit, owner)
12777        )
12778    };
12779    if resolves_target(&components) {
12780        return true;
12781    }
12782
12783    // Some real-world code spells a base mem-initializer as
12784    // `Base::Base(args)`. In that structured path the final component repeats
12785    // the constructor name; resolve the preceding type path. The terminal
12786    // identity check prevents an arbitrary qualified member from taking this
12787    // route.
12788    components
12789        .last()
12790        .is_some_and(|terminal| terminal == owner.identifier())
12791        && resolves_target(&components[..components.len() - 1])
12792}
12793
12794fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
12795    unit.signature()
12796        .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
12797        || ctx
12798            .analyzer
12799            .get_source(unit, false)
12800            .is_some_and(|declaration| {
12801                field_declaration_type_matches(&declaration, unit, ctx, owner)
12802            })
12803}
12804
12805pub fn field_declared_binding(
12806    analyzer: &CppGraphSource<'_>,
12807    visibility: &VisibilityIndex<'_>,
12808    visible_from: &ProjectFile,
12809    field: &CodeUnit,
12810) -> Option<CppScanBinding> {
12811    let fact = visibility.field_declared_type_fact(analyzer, field)?;
12812    let normalized = normalize_field_type_text(&fact.type_text);
12813    let resolved = visibility.resolve_unique_canonical_type_for_declaration(
12814        analyzer,
12815        visible_from,
12816        field,
12817        &normalized,
12818    );
12819    let resolved = match (resolved, fact.template_arguments.as_deref()) {
12820        (Some(primary), Some(arguments)) => visibility
12821            .resolve_template_arguments(visible_from, primary, arguments)
12822            .ok(),
12823        (resolved, None) => resolved,
12824        (None, Some(_)) => None,
12825    }
12826    .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
12827    Some(CppScanBinding::from_type_name(
12828        normalized,
12829        resolved,
12830        fact.indirection,
12831    ))
12832}
12833
12834/// Whether a member-initializer `field(args)` runs the declared type's
12835/// constructor: only a member that holds the type by value does. `Owner* p`
12836/// initialized as `p(nullptr)` binds a pointer and `Owner& r` as `r(other)`
12837/// binds a reference; neither runs an `Owner` constructor (#3286). A member
12838/// whose declaration the index cannot decode keeps the by-value reading, the
12839/// same assumption the usage scan's declared-type match makes.
12840pub fn field_initializer_holds_subobject(
12841    analyzer: &CppGraphSource<'_>,
12842    visibility: &VisibilityIndex<'_>,
12843    field: &CodeUnit,
12844) -> bool {
12845    visibility
12846        .field_declared_type_fact(analyzer, field)
12847        .is_none_or(|fact| !fact.binds_indirectly)
12848}
12849
12850/// Resolve the receiver type minted for a named declarator on an anonymous C
12851/// aggregate, such as `struct { int r; } c`. The declaration index preserves
12852/// the aggregate as the nested class `Owner$c`, but the field's type fact has
12853/// no spelling that can name that class. Confirm the anonymous aggregate from
12854/// its parsed declaration, then use the owner's structured child relationship
12855/// to recover the one corresponding receiver type.
12856fn anonymous_aggregate_field_owner(
12857    analyzer: &CppGraphSource<'_>,
12858    visibility: &VisibilityIndex<'_>,
12859    visible_from: &ProjectFile,
12860    field: &CodeUnit,
12861) -> Option<CodeUnit> {
12862    let owner = type_owner_of(analyzer, field)?;
12863    if !owner.is_class() {
12864        return None;
12865    }
12866    let declaration = analyzer.get_source(field, false)?;
12867    let mut parser = Parser::new();
12868    parser
12869        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12870        .ok()?;
12871    let tree = parser.parse(&declaration, None)?;
12872    let mut stack = vec![tree.root_node()];
12873    while let Some(node) = stack.pop() {
12874        if matches!(node.kind(), "declaration" | "field_declaration")
12875            && let Some(type_node) = node
12876                .child_by_field_name("type")
12877                .or_else(|| first_type_child(node))
12878            && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
12879            && type_node.child_by_field_name("name").is_none()
12880            && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
12881        {
12882            let matches = visibility
12883                .visible_members_for_owner_name(visible_from, &owner, field.identifier())
12884                .into_iter()
12885                .filter(|child| child.is_class() && child.identifier() == field.identifier())
12886                .collect::<Vec<_>>();
12887            return match matches.as_slice() {
12888                [child] => Some((*child).clone()),
12889                _ => None,
12890            };
12891        }
12892        let mut cursor = node.walk();
12893        stack.extend(node.named_children(&mut cursor));
12894    }
12895    None
12896}
12897
12898/// Resolve an anonymous C aggregate's generated owner from its declaration
12899/// range. Anonymous local structs and unions have no type name to enter into
12900/// the visibility index; declaration extraction gives them a structured class
12901/// identity keyed by the aggregate node's exact CST range instead. Matching
12902/// that range keeps nested aggregates and unrelated generated owners out of
12903/// the result without inspecting source text.
12904pub fn anonymous_aggregate_owner(
12905    analyzer: &CppGraphSource<'_>,
12906    file: &ProjectFile,
12907    node: Node<'_>,
12908) -> Option<CodeUnit> {
12909    if !matches!(node.kind(), "struct_specifier" | "union_specifier")
12910        || node.child_by_field_name("name").is_some()
12911    {
12912        return None;
12913    }
12914    let mut candidates = analyzer
12915        .declarations(file)
12916        .into_iter()
12917        .filter(|candidate| {
12918            candidate.is_class()
12919                && analyzer.ranges(candidate).into_iter().any(|range| {
12920                    range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
12921                })
12922        })
12923        .collect::<Vec<_>>();
12924    candidates.sort_by_key(|candidate| candidate.fq_name());
12925    candidates.dedup();
12926    match candidates.as_slice() {
12927        [candidate] => Some(candidate.clone()),
12928        _ => None,
12929    }
12930}
12931
12932/// The one logical type the candidates name, or why they do not name one.
12933fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
12934    let Some(first) = candidates.first() else {
12935        return Err(TypeCandidateFailure::Unresolvable);
12936    };
12937    if candidates
12938        .iter()
12939        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
12940    {
12941        Ok((*first).clone())
12942    } else {
12943        Err(TypeCandidateFailure::Ambiguous)
12944    }
12945}
12946
12947fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
12948    logical_type_candidate(candidates).ok()
12949}
12950
12951fn unique_type_candidate_preserving_alias(
12952    analyzer: &CppGraphSource<'_>,
12953    file: &ProjectFile,
12954    candidates: &[&CodeUnit],
12955) -> Option<CodeUnit> {
12956    let first = *candidates.first()?;
12957    if declared_type_alias(analyzer, first) {
12958        return candidates
12959            .iter()
12960            .all(|candidate| {
12961                declared_type_alias(analyzer, candidate)
12962                    && candidate.kind() == first.kind()
12963                    && candidate.fq_name() == first.fq_name()
12964                    && candidate.source() == first.source()
12965            })
12966            .then(|| first.clone());
12967    }
12968    if analyzer.reference_uses_c_semantics(file)
12969        && first.is_class()
12970        && indexed_c_tag_kind(analyzer, first).is_some()
12971    {
12972        let mut full_source = None;
12973        let mut tag_kind = None;
12974        for candidate in candidates.iter().copied() {
12975            let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
12976            if tag_kind
12977                .replace(candidate_tag_kind)
12978                .is_some_and(|existing| existing != candidate_tag_kind)
12979            {
12980                return None;
12981            }
12982            if cpp_class_declaration_strength(analyzer, candidate)
12983                == CppClassDeclarationStrength::Full
12984                && full_source
12985                    .replace(candidate.source())
12986                    .is_some_and(|existing| existing != candidate.source())
12987            {
12988                return None;
12989            }
12990        }
12991    }
12992    candidates
12993        .iter()
12994        .all(|candidate| {
12995            !declared_type_alias(analyzer, candidate)
12996                && candidate.kind() == first.kind()
12997                && candidate.fq_name() == first.fq_name()
12998        })
12999        .then(|| first.clone())
13000}
13001
13002fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
13003    is_type_alias(unit)
13004        || analyzer
13005            .type_alias_provider()
13006            .is_some_and(|provider| provider.is_type_alias(unit))
13007}
13008
13009pub fn field_declared_type_binding(
13010    analyzer: &CppGraphSource<'_>,
13011    visibility: &VisibilityIndex<'_>,
13012    visible_from: &ProjectFile,
13013    field: &CodeUnit,
13014) -> Option<(String, Option<CodeUnit>, i32)> {
13015    let fact = visibility.field_declared_type_fact(analyzer, field)?;
13016    let normalized = normalize_field_type_text(&fact.type_text);
13017    let primary = visibility.resolve_unique_canonical_type_for_declaration(
13018        analyzer,
13019        visible_from,
13020        field,
13021        &normalized,
13022    );
13023    let resolved = match (primary, fact.template_arguments.as_deref()) {
13024        (Some(primary), Some(arguments)) => visibility
13025            .resolve_template_arguments(visible_from, primary, arguments)
13026            .ok(),
13027        (resolved, None) => resolved,
13028        (None, Some(_)) => None,
13029    };
13030    Some((normalized, resolved, fact.indirection))
13031}
13032
13033fn decode_field_declared_type_fact(
13034    analyzer: &CppGraphSource<'_>,
13035    field: &CodeUnit,
13036) -> Option<DeclaredFieldTypeFact> {
13037    let Some(declaration) = analyzer.get_source(field, false) else {
13038        return decode_indexed_field_declared_type_fact(analyzer, field);
13039    };
13040    let mut parser = Parser::new();
13041    parser
13042        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13043        .ok()?;
13044    // A field's indexed source is stored without its surrounding class body.
13045    // Give tree-sitter that grammatical context before checking recovery-only
13046    // field shapes such as `PyObject_HEAD Imaging image;`.
13047    let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
13048    let contextual_tree = parser.parse(&contextual_declaration, None)?;
13049    let mut stack = vec![contextual_tree.root_node()];
13050    while let Some(node) = stack.pop() {
13051        if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
13052            && node_text(recovered.name, &contextual_declaration) == field.identifier()
13053        {
13054            return Some(DeclaredFieldTypeFact {
13055                type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
13056                indirection: recovered.pointer_depth(),
13057                binds_indirectly: recovered.pointer_depth() > 0,
13058                template_arguments: None,
13059            });
13060        }
13061        if let Some(recovered) =
13062            recovered_function_like_field_declarator(node, &contextual_declaration)
13063            && node_text(recovered.name, &contextual_declaration) == field.identifier()
13064        {
13065            let type_node = node
13066                .child_by_field_name("type")
13067                .or_else(|| first_type_child(node))?;
13068            return Some(DeclaredFieldTypeFact {
13069                type_text: node_text(type_node, &contextual_declaration).to_string(),
13070                indirection: recovered.pointer_depth(),
13071                // A function-like declarator names a callable member, never a
13072                // subobject of the declared return type.
13073                binds_indirectly: true,
13074                template_arguments: cpp_template_reference_arguments(
13075                    type_node,
13076                    &contextual_declaration,
13077                ),
13078            });
13079        }
13080        if let Some(fact) =
13081            decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
13082        {
13083            return Some(fact);
13084        }
13085        let mut cursor = node.walk();
13086        stack.extend(node.named_children(&mut cursor));
13087    }
13088    let tree = parser.parse(&declaration, None)?;
13089    let mut stack = vec![tree.root_node()];
13090    while let Some(node) = stack.pop() {
13091        if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
13092        {
13093            return Some(fact);
13094        }
13095        let mut cursor = node.walk();
13096        stack.extend(node.named_children(&mut cursor));
13097    }
13098    None
13099}
13100
13101/// Decode a field whose generated owner prevents the ordinary source lookup
13102/// from selecting a standalone declaration. The indexed ranges still point
13103/// into the physical syntax tree, so recover the enclosing declaration from
13104/// that structure and apply the same declarator decoder to it.
13105fn decode_indexed_field_declared_type_fact(
13106    analyzer: &CppGraphSource<'_>,
13107    field: &CodeUnit,
13108) -> Option<DeclaredFieldTypeFact> {
13109    let cpp = analyzer.cpp?;
13110    let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
13111    let source = prepared.source();
13112    let root = prepared.tree().root_node();
13113    for range in analyzer.ranges(field) {
13114        let end = range.start_byte.saturating_add(1).min(source.len());
13115        let mut current = root.descendant_for_byte_range(range.start_byte, end);
13116        while let Some(node) = current {
13117            if matches!(node.kind(), "declaration" | "field_declaration")
13118                && let Some(fact) =
13119                    decode_declared_field_type_node(node, field.identifier(), source)
13120            {
13121                return Some(fact);
13122            }
13123            current = node.parent();
13124        }
13125    }
13126    None
13127}
13128
13129fn decode_declared_field_type_node(
13130    node: Node<'_>,
13131    field_name: &str,
13132    source: &str,
13133) -> Option<DeclaredFieldTypeFact> {
13134    if !matches!(node.kind(), "declaration" | "field_declaration") {
13135        return None;
13136    }
13137    let type_node = node
13138        .child_by_field_name("type")
13139        .or_else(|| first_type_child(node))?;
13140    let binding = declared_name_binding(node, type_node, field_name, source)?;
13141    let declared_type = if matches!(
13142        type_node.kind(),
13143        "class_specifier" | "struct_specifier" | "union_specifier"
13144    ) {
13145        type_node.child_by_field_name("name")
13146    } else {
13147        Some(type_node)
13148    };
13149    Some(DeclaredFieldTypeFact {
13150        type_text: declared_type.map_or_else(
13151            || field_name.to_string(),
13152            |declared_type| node_text(declared_type, source).to_string(),
13153        ),
13154        indirection: binding.pointer_depth,
13155        binds_indirectly: binding.indirect,
13156        template_arguments: declared_type
13157            .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
13158    })
13159}
13160
13161/// Text of the type that a C or C++ alias declaration names, read from the
13162/// `type_definition` or `alias_declaration` node's `type` field.
13163///
13164/// The declaration text is never scanned. A function-pointer typedef
13165/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
13166/// so no prefix or suffix of the spelling isolates the target.
13167///
13168/// An alias whose declarator is a function declarator names a function type:
13169/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
13170/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
13171/// so such an alias has no canonical target. Its `type` field holds the return
13172/// type `R`, which is a different type from the alias, so this returns `None`
13173/// rather than that return type.
13174pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
13175    let mut parser = Parser::new();
13176    parser
13177        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13178        .ok()?;
13179    let tree = parser.parse(declaration, None)?;
13180    let mut stack = vec![tree.root_node()];
13181    while let Some(node) = stack.pop() {
13182        let type_node = match node.kind() {
13183            "type_definition" => {
13184                let mut cursor = node.walk();
13185                if node
13186                    .children_by_field_name("declarator", &mut cursor)
13187                    .any(declarator_names_function_type)
13188                {
13189                    return None;
13190                }
13191                node.child_by_field_name("type")?
13192            }
13193            "alias_declaration" => {
13194                let type_node = node.child_by_field_name("type")?;
13195                if type_node
13196                    .child_by_field_name("declarator")
13197                    .is_some_and(declarator_names_function_type)
13198                {
13199                    return None;
13200                }
13201                type_node
13202            }
13203            _ => {
13204                let mut cursor = node.walk();
13205                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13206                stack.extend(children.into_iter().rev());
13207                continue;
13208            }
13209        };
13210        return Some(node_text(type_node, declaration).to_string());
13211    }
13212    None
13213}
13214
13215/// Whether an alias declaration's own declarator adds indirection that
13216/// [`cpp_alias_declaration_target_text`] does not report.
13217///
13218/// That function reads the declaration's `type` field, where `typedef Foo *Bar`
13219/// keeps only `Foo`: the `*` lives in the sibling declarator. Substituting such
13220/// an alias would equate `f(Bar)` with `f(Foo)`, so a comparison that cannot
13221/// prove the alias adds no indirection must refuse to follow it. A declaration
13222/// this cannot read at all is refused for the same reason.
13223fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
13224    let mut parser = Parser::new();
13225    if parser
13226        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13227        .is_err()
13228    {
13229        return true;
13230    }
13231    let Some(tree) = parser.parse(declaration, None) else {
13232        return true;
13233    };
13234    let mut stack = vec![tree.root_node()];
13235    while let Some(node) = stack.pop() {
13236        let declarators = match node.kind() {
13237            "type_definition" => {
13238                let mut cursor = node.walk();
13239                node.children_by_field_name("declarator", &mut cursor)
13240                    .collect::<Vec<_>>()
13241            }
13242            "alias_declaration" => node
13243                .child_by_field_name("type")
13244                .and_then(|type_node| type_node.child_by_field_name("declarator"))
13245                .into_iter()
13246                .collect::<Vec<_>>(),
13247            _ => {
13248                let mut cursor = node.walk();
13249                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13250                stack.extend(children.into_iter().rev());
13251                continue;
13252            }
13253        };
13254        return declarators.into_iter().any(cpp_declarator_adds_indirection);
13255    }
13256    true
13257}
13258
13259/// True when an alias declarator names a function type.
13260///
13261/// The declarator chain is walked through the `declarator` field, so the
13262/// parameter list -- a sibling field -- is never entered and a parameter's own
13263/// function declarator cannot be mistaken for the alias's.
13264fn declarator_names_function_type(declarator: Node<'_>) -> bool {
13265    let mut current = Some(declarator);
13266    while let Some(node) = current {
13267        match node.kind() {
13268            "function_declarator" | "abstract_function_declarator" => return true,
13269            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
13270                current = node.named_child(0);
13271            }
13272            _ => current = node.child_by_field_name("declarator"),
13273        }
13274    }
13275    false
13276}
13277
13278/// Whether one indexed field declaration is a function or function-pointer
13279/// value. This follows tree-sitter declarator fields and never infers
13280/// callability from source spelling.
13281pub fn cpp_field_declaration_names_function_type(declaration: &str, field_name: &str) -> bool {
13282    let mut parser = Parser::new();
13283    if parser
13284        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13285        .is_err()
13286    {
13287        return false;
13288    }
13289    let Some(tree) = parser.parse(declaration, None) else {
13290        return false;
13291    };
13292    let mut stack = vec![tree.root_node()];
13293    while let Some(node) = stack.pop() {
13294        if matches!(node.kind(), "declaration" | "field_declaration") {
13295            let mut cursor = node.walk();
13296            if node
13297                .children_by_field_name("declarator", &mut cursor)
13298                .any(|declarator| {
13299                    declarator_name_node(declarator).is_some_and(|name| {
13300                        node_text(name, declaration) == field_name
13301                            && declarator_names_function_type(declarator)
13302                    })
13303                })
13304            {
13305                return true;
13306            }
13307        }
13308        let mut cursor = node.walk();
13309        stack.extend(node.named_children(&mut cursor));
13310    }
13311    false
13312}
13313
13314/// Whether one indexed alias declaration names a function or function-pointer
13315/// type. The alias name is matched through the declarator field so a function
13316/// type used by a parameter cannot be mistaken for the alias itself.
13317pub fn cpp_alias_declaration_names_function_type(declaration: &str, alias_name: &str) -> bool {
13318    let mut parser = Parser::new();
13319    if parser
13320        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13321        .is_err()
13322    {
13323        return false;
13324    }
13325    let Some(tree) = parser.parse(declaration, None) else {
13326        return false;
13327    };
13328    let mut stack = vec![tree.root_node()];
13329    while let Some(node) = stack.pop() {
13330        match node.kind() {
13331            "type_definition" => {
13332                let mut cursor = node.walk();
13333                if node
13334                    .children_by_field_name("declarator", &mut cursor)
13335                    .any(|declarator| {
13336                        extract_typedef_declarator_name(declarator, declaration)
13337                            .is_some_and(|name| name == alias_name)
13338                            && declarator_names_function_type(declarator)
13339                    })
13340                {
13341                    return true;
13342                }
13343            }
13344            "alias_declaration" => {
13345                let names_alias = node
13346                    .child_by_field_name("name")
13347                    .is_some_and(|name| node_text(name, declaration) == alias_name);
13348                if names_alias
13349                    && node
13350                        .child_by_field_name("type")
13351                        .and_then(|type_node| type_node.child_by_field_name("declarator"))
13352                        .is_some_and(declarator_names_function_type)
13353                {
13354                    return true;
13355                }
13356            }
13357            _ => {}
13358        }
13359        let mut cursor = node.walk();
13360        stack.extend(node.named_children(&mut cursor));
13361    }
13362    false
13363}
13364
13365fn decode_structured_alias_target(
13366    analyzer: &CppGraphSource<'_>,
13367    unit: &CodeUnit,
13368) -> Option<StructuredAliasTarget> {
13369    analyzer
13370        .get_source(unit, false)
13371        .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
13372        .or_else(|| {
13373            let signature = unit.signature()?;
13374            decode_structured_alias_target_source(unit, signature, false)
13375        })
13376}
13377
13378fn decode_structured_alias_target_source(
13379    unit: &CodeUnit,
13380    declaration: &str,
13381    require_top_level: bool,
13382) -> Option<StructuredAliasTarget> {
13383    let mut parser = Parser::new();
13384    parser
13385        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13386        .ok()?;
13387    let tree = parser.parse(declaration, None)?;
13388    let mut stack = vec![tree.root_node()];
13389    while let Some(node) = stack.pop() {
13390        let type_node = match node.kind() {
13391            "type_definition" => {
13392                if require_top_level
13393                    && node
13394                        .parent()
13395                        .is_none_or(|parent| parent.kind() != "translation_unit")
13396                {
13397                    let mut cursor = node.walk();
13398                    stack.extend(node.named_children(&mut cursor));
13399                    continue;
13400                }
13401                let mut declarator_cursor = node.walk();
13402                let declarator = node
13403                    .children_by_field_name("declarator", &mut declarator_cursor)
13404                    .find(|declarator| {
13405                        extract_typedef_declarator_name(*declarator, declaration)
13406                            .is_some_and(|name| name == unit.identifier())
13407                    })?;
13408                if declarator_names_function_type(declarator) {
13409                    return None;
13410                }
13411                node.child_by_field_name("type")?
13412            }
13413            "alias_declaration" => {
13414                if require_top_level
13415                    && node
13416                        .parent()
13417                        .is_none_or(|parent| parent.kind() != "translation_unit")
13418                {
13419                    let mut cursor = node.walk();
13420                    stack.extend(node.named_children(&mut cursor));
13421                    continue;
13422                }
13423                let name = node.child_by_field_name("name")?;
13424                if node_text(name, declaration) != unit.identifier() {
13425                    return None;
13426                }
13427                let type_node = node.child_by_field_name("type")?;
13428                if type_node
13429                    .child_by_field_name("declarator")
13430                    .is_some_and(declarator_names_function_type)
13431                {
13432                    return None;
13433                }
13434                type_node
13435            }
13436            _ => {
13437                let mut cursor = node.walk();
13438                stack.extend(node.named_children(&mut cursor));
13439                continue;
13440            }
13441        };
13442        return structured_alias_type_target(type_node, declaration);
13443    }
13444    None
13445}
13446
13447fn structured_alias_type_target(
13448    mut type_node: Node<'_>,
13449    source: &str,
13450) -> Option<StructuredAliasTarget> {
13451    while type_node.kind() == "type_descriptor" {
13452        type_node = type_node.child_by_field_name("type")?;
13453    }
13454    if type_node.kind() == "primitive_type" {
13455        return Some(StructuredAliasTarget::Builtin);
13456    }
13457    if matches!(
13458        type_node.kind(),
13459        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13460    ) {
13461        type_node = type_node.child_by_field_name("name")?;
13462    }
13463    let global = type_node.child_by_field_name("scope").is_none()
13464        && type_node.child(0).is_some_and(|child| child.kind() == "::");
13465    let mut components = Vec::new();
13466    append_structured_type_components(type_node, source, &mut components)?;
13467    let arguments = cpp_template_reference_arguments(type_node, source);
13468    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
13469        components,
13470        global,
13471        arguments,
13472    })
13473}
13474
13475fn append_structured_type_components(
13476    node: Node<'_>,
13477    source: &str,
13478    out: &mut Vec<String>,
13479) -> Option<()> {
13480    match node.kind() {
13481        "identifier" | "namespace_identifier" | "type_identifier" => {
13482            out.push(node_text(node, source).to_string());
13483            Some(())
13484        }
13485        "template_type" => {
13486            append_structured_type_components(node.child_by_field_name("name")?, source, out)
13487        }
13488        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13489            if let Some(scope) = node.child_by_field_name("scope") {
13490                append_structured_type_components(scope, source, out)?;
13491            }
13492            append_structured_type_components(node.child_by_field_name("name")?, source, out)
13493        }
13494        _ => None,
13495    }
13496}
13497
13498/// How the declarator that names a declared entity binds the declaration's
13499/// type.
13500pub(crate) struct DeclaredNameBinding {
13501    /// Pointer declarators between the type and the name.
13502    pub pointer_depth: i32,
13503    /// The name binds the declared type through a pointer or a reference, so
13504    /// the declared entity holds no subobject of that type. An array of the
13505    /// type does hold subobjects, so it is not indirect.
13506    pub indirect: bool,
13507}
13508
13509pub(crate) fn declared_name_binding(
13510    declaration: Node<'_>,
13511    type_node: Node<'_>,
13512    field_name: &str,
13513    source: &str,
13514) -> Option<DeclaredNameBinding> {
13515    let mut stack = Vec::new();
13516    let mut cursor = declaration.walk();
13517    stack.extend(
13518        declaration
13519            .named_children(&mut cursor)
13520            .filter(|child| !same_node(*child, type_node)),
13521    );
13522    while let Some(node) = stack.pop() {
13523        if matches!(node.kind(), "identifier" | "field_identifier")
13524            && node_text(node, source) == field_name
13525        {
13526            let mut binding = DeclaredNameBinding {
13527                pointer_depth: 0,
13528                indirect: false,
13529            };
13530            let mut current = node.parent();
13531            while let Some(parent) = current {
13532                if same_node(parent, declaration) {
13533                    return Some(binding);
13534                }
13535                match parent.kind() {
13536                    "pointer_declarator" => {
13537                        binding.pointer_depth += 1;
13538                        binding.indirect = true;
13539                    }
13540                    "reference_declarator" => binding.indirect = true,
13541                    _ => {}
13542                }
13543                current = parent.parent();
13544            }
13545            return None;
13546        }
13547        let mut cursor = node.walk();
13548        stack.extend(node.named_children(&mut cursor));
13549    }
13550    None
13551}
13552
13553fn field_declaration_type_matches(
13554    declaration: &str,
13555    unit: &CodeUnit,
13556    ctx: &ScanCtx<'_>,
13557    owner: &CodeUnit,
13558) -> bool {
13559    ctx.visibility
13560        .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
13561        || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
13562            let normalized = normalize_field_type_text(type_text);
13563            ctx.visibility
13564                .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
13565                || ctx.visibility.resolves_to_type(
13566                    &ctx.analyzer,
13567                    ctx.file,
13568                    normalized.as_str(),
13569                    owner,
13570                )
13571        })
13572}
13573
13574fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
13575    let declaration = declaration
13576        .split(['=', ';'])
13577        .next()
13578        .unwrap_or(declaration)
13579        .trim();
13580    let index = declaration.rfind(field_name)?;
13581    let before = &declaration[..index];
13582    let after = &declaration[index + field_name.len()..];
13583    if before.chars().next_back().is_some_and(is_identifier_char)
13584        || after.chars().next().is_some_and(is_identifier_char)
13585    {
13586        return None;
13587    }
13588    Some(before.trim())
13589}
13590
13591fn normalize_field_type_text(type_text: &str) -> String {
13592    const FIELD_SPECIFIERS: [&str; 8] = [
13593        "extern ",
13594        "static ",
13595        "mutable ",
13596        "constexpr ",
13597        "constinit ",
13598        "inline ",
13599        "volatile ",
13600        "const ",
13601    ];
13602
13603    let mut normalized = normalize_type_text(type_text);
13604    loop {
13605        let Some(stripped) = FIELD_SPECIFIERS
13606            .iter()
13607            .find_map(|specifier| normalized.strip_prefix(specifier))
13608        else {
13609            return normalized;
13610        };
13611        normalized = normalize_type_text(stripped);
13612    }
13613}
13614
13615fn is_identifier_char(ch: char) -> bool {
13616    ch == '_' || ch.is_ascii_alphanumeric()
13617}
13618
13619pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13620    let Some(type_node) = node.child_by_field_name("type") else {
13621        return false;
13622    };
13623    ctx.visibility.resolves_to_type(
13624        &ctx.analyzer,
13625        ctx.file,
13626        node_text(type_node, ctx.source),
13627        owner,
13628    )
13629}
13630
13631pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13632    !ctx.analyzer
13633        .declarations(ctx.file)
13634        .into_iter()
13635        .filter(|unit| unit.is_function())
13636        .any(|unit| {
13637            ctx.analyzer.ranges(&unit).iter().any(|range| {
13638                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13639            })
13640        })
13641}
13642
13643/// How a `T var ...;` declaration initializes its object.
13644pub enum DeclarationConstructorInitializer<'tree> {
13645    /// Direct initialization, `T var(args)` or `T var{args}`: the argument list
13646    /// the declaration hands the constructor.
13647    Arguments(Node<'tree>),
13648    /// Copy initialization from one expression, `T var = expr`, which supplies a
13649    /// single constructor argument without spelling an argument list.
13650    Expression(Node<'tree>),
13651    /// `T var;`, which names no constructor argument at all.
13652    Empty,
13653}
13654
13655pub fn declaration_constructor_initializer(
13656    node: Node<'_>,
13657) -> DeclarationConstructorInitializer<'_> {
13658    let mut cursor = node.walk();
13659    for child in node.named_children(&mut cursor) {
13660        if child.kind() == "init_declarator" {
13661            let Some(value) = child
13662                .child_by_field_name("value")
13663                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13664                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13665            else {
13666                return DeclarationConstructorInitializer::Empty;
13667            };
13668            return match value.kind() {
13669                "argument_list" | "initializer_list" => {
13670                    DeclarationConstructorInitializer::Arguments(value)
13671                }
13672                "compound_literal_expression" => call_arguments_node(value)
13673                    .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13674                        DeclarationConstructorInitializer::Arguments(arguments)
13675                    }),
13676                _ => DeclarationConstructorInitializer::Expression(value),
13677            };
13678        }
13679        if let Some(declarator) = declaration_declarator(node, child) {
13680            return declarator_parameters(declarator)
13681                .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13682                    DeclarationConstructorInitializer::Arguments(parameters)
13683                });
13684        }
13685    }
13686    DeclarationConstructorInitializer::Empty
13687}
13688
13689pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13690    match declaration_constructor_initializer(node) {
13691        DeclarationConstructorInitializer::Arguments(arguments) => {
13692            argument_children(arguments).count()
13693        }
13694        DeclarationConstructorInitializer::Expression(_) => 1,
13695        DeclarationConstructorInitializer::Empty => 0,
13696    }
13697}
13698
13699/// The parameter list of the innermost declarator, which is where a
13700/// `T var(args)` declaration parsed as a function declarator keeps the
13701/// constructor arguments.
13702fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13703    let mut current = node;
13704    loop {
13705        if let Some(parameters) = current.child_by_field_name("parameters") {
13706            return Some(parameters);
13707        }
13708        current = current.child_by_field_name("declarator")?;
13709    }
13710}
13711
13712pub(super) fn first_named_child_of_kind<'tree>(
13713    node: Node<'tree>,
13714    kind: &str,
13715) -> Option<Node<'tree>> {
13716    let mut cursor = node.walk();
13717    node.named_children(&mut cursor)
13718        .find(|child| child.kind() == kind)
13719}
13720
13721fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13722    let mut stack = vec![root];
13723    while let Some(node) = stack.pop() {
13724        if node.kind() == kind {
13725            return Some(node);
13726        }
13727        push_named_children_reversed(node, &mut stack);
13728    }
13729    None
13730}
13731
13732fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13733    if node.kind() == "identifier" {
13734        return true;
13735    }
13736    if node.kind() == "parenthesized_expression" {
13737        return false;
13738    }
13739    if node.kind() == "call_expression" {
13740        return node
13741            .child_by_field_name("function")
13742            .is_some_and(|function| function.kind() == "identifier");
13743    }
13744    let mut stack = vec![node];
13745    while let Some(descendant) = stack.pop() {
13746        if descendant != node && descendant.kind() == "parenthesized_expression" {
13747            continue;
13748        }
13749        if descendant.kind() == "identifier" {
13750            return true;
13751        }
13752        if descendant.kind() == "call_expression" {
13753            if descendant
13754                .child_by_field_name("function")
13755                .is_some_and(|function| function.kind() == "identifier")
13756            {
13757                return true;
13758            }
13759            continue;
13760        }
13761        push_named_children_reversed(descendant, &mut stack);
13762    }
13763    false
13764}
13765
13766fn macro_expansion_shape_is_safe(
13767    node: Node<'_>,
13768    source: &str,
13769    parameters: &[String],
13770    environment: &MacroEnvironment,
13771) -> bool {
13772    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
13773        return true;
13774    }
13775    if node.kind() == "call_expression" {
13776        let Some(function) = node.child_by_field_name("function") else {
13777            return true;
13778        };
13779        if function.kind() != "identifier" {
13780            return true;
13781        }
13782        let function_name = node_text(function, source);
13783        if parameters
13784            .iter()
13785            .any(|parameter| parameter == function_name)
13786        {
13787            return false;
13788        }
13789        if !environment.may_bind(function_name) {
13790            return true;
13791        }
13792        let Some(arguments) = node.child_by_field_name("arguments") else {
13793            return false;
13794        };
13795        return argument_children(arguments).all(|argument| {
13796            if argument.kind() == "identifier"
13797                && parameters
13798                    .iter()
13799                    .any(|parameter| parameter == node_text(argument, source))
13800            {
13801                return false;
13802            }
13803            macro_expansion_shape_is_safe(argument, source, parameters, environment)
13804        });
13805    }
13806    let mut stack = vec![node];
13807    while let Some(descendant) = stack.pop() {
13808        if descendant != node {
13809            if descendant.kind() == "parenthesized_expression" {
13810                continue;
13811            }
13812            if descendant.kind() == "call_expression" {
13813                let expands = descendant
13814                    .child_by_field_name("function")
13815                    .filter(|function| function.kind() == "identifier")
13816                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
13817                if expands {
13818                    return false;
13819                }
13820                continue;
13821            }
13822        }
13823        if descendant.kind() == "identifier" {
13824            let identifier = node_text(descendant, source);
13825            if parameters.iter().any(|parameter| parameter == identifier)
13826                || environment.may_bind(identifier)
13827            {
13828                return false;
13829            }
13830        }
13831        push_named_children_reversed(descendant, &mut stack);
13832    }
13833    true
13834}
13835
13836fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
13837    let text = node_text(path, source);
13838    match path.kind() {
13839        "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
13840        "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
13841        _ => None,
13842    }
13843}
13844
13845fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
13846    let source = prepared.source();
13847    let mut facts = Vec::new();
13848    let mut nodes = vec![prepared.tree().root_node()];
13849    while let Some(node) = nodes.pop() {
13850        if node.kind() == "preproc_include" {
13851            let Some(path) = node
13852                .child_by_field_name("path")
13853                .and_then(|path| structured_include_path(path, source))
13854                .map(str::to_owned)
13855            else {
13856                continue;
13857            };
13858            facts.push(StructuredIncludeFact {
13859                start_byte: node.start_byte(),
13860                end_byte: node.end_byte(),
13861                path,
13862            });
13863            continue;
13864        }
13865        push_named_children_reversed(node, &mut nodes);
13866    }
13867    Arc::from(facts.into_boxed_slice())
13868}
13869
13870fn has_unresolved_include_visible_before_in_prepared(
13871    file: &ProjectFile,
13872    prepared: &PreparedSyntaxTree,
13873    include_targets: &IncludeTargetIndex,
13874    facts: &[StructuredIncludeFact],
13875    before_byte: usize,
13876) -> bool {
13877    let guards = OnceCell::new();
13878    let reference = CallableReferenceContext {
13879        file,
13880        position: Some(CallableReferencePosition {
13881            prepared,
13882            byte: before_byte,
13883            guards: &guards,
13884        }),
13885    };
13886    let root = prepared.tree().root_node();
13887    facts
13888        .iter()
13889        .filter(|fact| fact.end_byte <= before_byte)
13890        .any(|fact| {
13891            let node = root
13892                .descendant_for_byte_range(fact.start_byte, fact.end_byte)
13893                .expect("structured include fact range must be in prepared tree");
13894            assert_eq!(
13895                node.kind(),
13896                "preproc_include",
13897                "structured include fact range must identify its include node"
13898            );
13899            callable_preprocessor_context_is_visible_for_reference(
13900                node,
13901                prepared.source(),
13902                &reference,
13903            ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
13904        })
13905}
13906
13907fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
13908    let descendant = node;
13909    while let Some(parent) = node.parent() {
13910        if is_preprocessor_conditional(parent)
13911            && !is_file_covering_include_guard(parent, source)
13912            && preprocessor_conditional_contains_descendant(parent, descendant)
13913        {
13914            return true;
13915        }
13916        node = parent;
13917    }
13918    false
13919}
13920
13921/// The [`OwningPreprocessorConditionals`] of a macro event at `event`.
13922///
13923/// The descendant this walks up from is the one
13924/// [`VisibilityIndex::macro_event_condition_value`] starts from -- the
13925/// innermost node at the event's first byte, not the event node itself --
13926/// because containment compares that descendant's end against the recovered
13927/// conditional boundary, and the two nodes end in different places. An event
13928/// that [`has_preprocessor_conditional_ancestor`] rejects owns nothing: that
13929/// predicate is what has always decided whether an event is conditional at
13930/// all, and answering it first also skips the walk for the ordinary
13931/// unconditional event.
13932fn owning_preprocessor_conditionals(
13933    root: Node<'_>,
13934    event: Node<'_>,
13935    source: &str,
13936) -> OwningPreprocessorConditionals {
13937    if !has_preprocessor_conditional_ancestor(event, source) {
13938        return OwningPreprocessorConditionals::default();
13939    }
13940    let start = event.start_byte();
13941    let descendant = root
13942        .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
13943        .expect("a byte inside the parsed tree names a descendant");
13944    let mut owners = Vec::new();
13945    let mut current = descendant.parent();
13946    while let Some(conditional) = current {
13947        if is_preprocessor_conditional(conditional)
13948            && !is_file_covering_include_guard(conditional, source)
13949            && preprocessor_conditional_contains_descendant(conditional, descendant)
13950        {
13951            owners.push(conditional.start_byte());
13952        }
13953        current = conditional.parent();
13954    }
13955    owners.into_boxed_slice()
13956}
13957
13958fn is_preprocessor_conditional(node: Node<'_>) -> bool {
13959    matches!(
13960        node.kind(),
13961        "preproc_if"
13962            | "preproc_ifdef"
13963            | "preproc_ifndef"
13964            | "preproc_elif"
13965            | "preproc_elifdef"
13966            | "preproc_else"
13967    )
13968}
13969
13970fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
13971    node.parent()
13972        .filter(|parent| parent.kind() == "translation_unit")
13973        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
13974        && is_canonical_include_guard(node, source)
13975}
13976
13977fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
13978    if node.kind() != "preproc_ifdef"
13979        || node
13980            .child(0)
13981            .is_none_or(|directive| directive.kind() != "#ifndef")
13982        || node.child_by_field_name("alternative").is_some()
13983    {
13984        return false;
13985    }
13986    let Some(guard_name) = node.child_by_field_name("name") else {
13987        return false;
13988    };
13989    let mut cursor = node.walk();
13990    node.named_children(&mut cursor)
13991        .find(|child| *child != guard_name && child.kind() != "comment")
13992        .filter(|child| child.kind() == "preproc_def")
13993        .and_then(|definition| definition.child_by_field_name("name"))
13994        .is_some_and(|defined_name| {
13995            node_text(defined_name, source) == node_text(guard_name, source)
13996        })
13997}
13998
13999fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14000    let mut guard = None;
14001    for child in named_children_iter(root) {
14002        if child.kind() == "comment" || is_pragma_once(child, source) {
14003            continue;
14004        }
14005        if guard.is_none() && is_canonical_include_guard(child, source) {
14006            guard = Some(child);
14007        } else {
14008            return None;
14009        }
14010    }
14011    guard
14012        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14013        .map(|name| node_text(name, source).to_string())
14014}
14015
14016fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14017    if (0..root.named_child_count())
14018        .filter_map(|index| root.named_child(index))
14019        .any(|child| is_pragma_once(child, source))
14020    {
14021        return MacroIncludeProtection::PragmaOnce;
14022    }
14023    top_level_canonical_include_guard_name(root, source)
14024        .map(MacroIncludeProtection::MacroGuard)
14025        .unwrap_or(MacroIncludeProtection::None)
14026}
14027
14028fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
14029    node.kind() == "preproc_call"
14030        && node
14031            .child_by_field_name("directive")
14032            .is_some_and(|directive| node_text(directive, source) == "#pragma")
14033        && node
14034            .child_by_field_name("argument")
14035            .is_some_and(|argument| node_text(argument, source).trim() == "once")
14036}
14037
14038fn parse_preproc_identifier(argument: &str) -> Option<String> {
14039    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
14040    let mut parser = Parser::new();
14041    parser
14042        .set_language(&tree_sitter_cpp::LANGUAGE.into())
14043        .ok()?;
14044    let tree = parser.parse(&sentinel, None)?;
14045    if tree.root_node().has_error() {
14046        return None;
14047    }
14048    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
14049    let identifier = statement.named_child(0)?;
14050    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
14051        .then(|| node_text(identifier, &sentinel).to_string())
14052}
14053
14054pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
14055    match node.kind() {
14056        "identifier" | "field_identifier" => {
14057            let name = node_text(node, source).trim();
14058            (!name.is_empty()).then(|| name.to_string())
14059        }
14060        "abstract_array_declarator"
14061        | "abstract_function_declarator"
14062        | "abstract_parenthesized_declarator"
14063        | "abstract_pointer_declarator"
14064        | "abstract_reference_declarator" => None,
14065        "function_declarator" => node
14066            .child_by_field_name("declarator")
14067            .or_else(|| node.child_by_field_name("name"))
14068            .and_then(|child| extract_variable_name(child, source)),
14069        _ => node
14070            .child_by_field_name("declarator")
14071            .or_else(|| node.child_by_field_name("name"))
14072            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
14073            .and_then(|child| extract_variable_name(child, source)),
14074    }
14075}
14076
14077/// Whether `file` is proven to use plain-C source semantics.
14078///
14079/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
14080/// compilation dialect on their own, so only an exact `.c` source extension is
14081/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
14082/// identifiers.
14083///
14084/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
14085/// which extraction reads too (a `.c` file is extracted with C tag scope), so
14086/// the doctrine has exactly one definition.
14087pub fn is_c_source_file(file: &ProjectFile) -> bool {
14088    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
14089}
14090
14091/// Whether tree-sitter parsed the operand of C `sizeof(T)` as an expression
14092/// identifier even though `T` may denote a typedef.
14093///
14094/// The grammar cannot distinguish `sizeof(value)` from `sizeof(Type)` without
14095/// semantic information. Keep this helper structural and narrow; callers must
14096/// still prove a visible type and reject an active ordinary-namespace shadow.
14097pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
14098    if !is_c_source_file(file) || node.kind() != "identifier" {
14099        return false;
14100    }
14101    let mut operand = node;
14102    while let Some(parent) = operand.parent().filter(|parent| {
14103        parent.kind() == "parenthesized_expression"
14104            && parent.named_child_count() == 1
14105            && parent.named_child(0) == Some(operand)
14106    }) {
14107        operand = parent;
14108    }
14109    operand.parent().is_some_and(|parent| {
14110        parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
14111    })
14112}
14113
14114/// Return the type and member leaves of a C `offsetof` member designator.
14115///
14116/// `offsetof_expression` is a dedicated tree-sitter node, so its two operands
14117/// must be interpreted through their named fields.  In particular, do not
14118/// infer the aggregate from the enclosing lexical scope: an `offsetof` can
14119/// name a member of an unrelated aggregate, including a field promoted from
14120/// an anonymous union.  A missing or unsupported operand is deliberately
14121/// rejected so callers can keep the reference unresolved.
14122pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
14123    if node.kind() != "field_identifier" {
14124        return None;
14125    }
14126    let expression = node.parent().filter(|parent| {
14127        parent.kind() == "offsetof_expression" && parent.child_by_field_name("member") == Some(node)
14128    })?;
14129    if expression.has_error() {
14130        return None;
14131    }
14132    let closing = expression.child(expression.child_count().saturating_sub(1))?;
14133    if closing.kind() != ")" || closing.is_missing() {
14134        return None;
14135    }
14136    let type_descriptor = expression.child_by_field_name("type")?;
14137    if type_descriptor.kind() != "type_descriptor"
14138        || type_descriptor.is_missing()
14139        || type_descriptor.has_error()
14140    {
14141        return None;
14142    }
14143    let type_specifier = type_descriptor.child_by_field_name("type")?;
14144    if type_specifier.is_missing() || type_specifier.has_error() {
14145        return None;
14146    }
14147    let type_reference = match type_specifier.kind() {
14148        "class_specifier" | "struct_specifier" | "union_specifier" => {
14149            type_specifier.child_by_field_name("name")?
14150        }
14151        _ => type_specifier,
14152    };
14153    (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
14154}
14155
14156/// Whether `node` is the member leaf of an `offsetof_expression`, including a
14157/// malformed type operand.  Callers use this guard to prevent the ordinary
14158/// field-name heuristics from guessing an owner after structured resolution
14159/// has failed.
14160pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
14161    node.kind() == "field_identifier"
14162        && node.parent().is_some_and(|parent| {
14163            parent.kind() == "offsetof_expression"
14164                && parent.child_by_field_name("member") == Some(node)
14165        })
14166}
14167
14168/// Whether `node` is a template argument name that tree-sitter spelled with
14169/// type syntax.
14170///
14171/// The grammar cannot tell a type argument from a non-type (value) argument, so
14172/// it gives both the same shape:
14173/// `template_argument_list -> type_descriptor -> type_identifier`. In
14174/// `std::array<W, N>` the type `W` and the constant `N` parse identically, and
14175/// so do `std::span<const uint8_t, ED448_LEN>`'s length and a nested type
14176/// member used as a real type argument.
14177///
14178/// This helper reports only the syntactic position. A caller must still prove
14179/// which namespace explains the spelling: forward navigation asks the type
14180/// namespace first and reads the leaf as a value only when no type explains it,
14181/// and the inverse field scan admits the leaf only when no visible type does
14182/// (#2556).
14183pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
14184    if node.kind() != "type_identifier" {
14185        return false;
14186    }
14187    let Some(descriptor) = node
14188        .parent()
14189        .filter(|parent| parent.kind() == "type_descriptor")
14190    else {
14191        return false;
14192    };
14193    if descriptor.child_by_field_name("type") != Some(node) {
14194        return false;
14195    }
14196    let Some(arguments) = descriptor
14197        .parent()
14198        .filter(|parent| parent.kind() == "template_argument_list")
14199    else {
14200        return false;
14201    };
14202    arguments.parent().is_some_and(|owner| {
14203        matches!(
14204            owner.kind(),
14205            "template_type" | "template_function" | "template_method"
14206        ) && owner.child_by_field_name("arguments") == Some(arguments)
14207    })
14208}
14209
14210/// Whether a reference written in `file` reads C++ source with C semantics.
14211///
14212/// [`is_c_source_file`] answers the half a path settles on its own. The other
14213/// half is a header, which has no dialect of its own: it is read as C exactly
14214/// when every workspace translation unit that provably compiles it compiles it
14215/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
14216///
14217/// This is the gate for anything that is really about the compilation
14218/// language of the code being read -- which reading of an included header's
14219/// declarations is in scope, whether `this` is an ordinary identifier. It is
14220/// NOT the gate for a question that is genuinely about a `.c` file on disk;
14221/// those keep calling [`is_c_source_file`].
14222pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
14223    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
14224}
14225
14226pub fn is_declarator_node(node: Node<'_>) -> bool {
14227    matches!(
14228        node.kind(),
14229        "identifier"
14230            | "field_identifier"
14231            | "qualified_identifier"
14232            | "scoped_identifier"
14233            | "pointer_declarator"
14234            | "reference_declarator"
14235            | "array_declarator"
14236            | "parenthesized_declarator"
14237            | "function_declarator"
14238    )
14239}
14240
14241/// Returns the declarator represented by one direct child of a declaration.
14242///
14243/// Tree-sitter exposes the first declarator through the `declarator` field,
14244/// but subsequent comma-separated declarators are unfielded direct children:
14245/// `int first, second;` therefore has an `identifier` child for `first` and a
14246/// second, otherwise identical, `identifier` child for `second`. Consumers
14247/// must inspect both shapes or a later declarator can be mistaken for a use of
14248/// an unrelated indexed symbol.
14249pub fn declaration_declarator<'tree>(
14250    declaration: Node<'tree>,
14251    child: Node<'tree>,
14252) -> Option<Node<'tree>> {
14253    if !matches!(
14254        declaration.kind(),
14255        "declaration"
14256            | "field_declaration"
14257            | "parameter_declaration"
14258            | "optional_parameter_declaration"
14259            | "function_definition"
14260            | "type_definition"
14261            | "alias_declaration"
14262            | "template_instantiation"
14263    ) {
14264        return None;
14265    }
14266    if declaration
14267        .child_by_field_name("type")
14268        .is_some_and(|type_node| same_node(type_node, child))
14269    {
14270        return None;
14271    }
14272    if child.kind() == "init_declarator" {
14273        return child.child_by_field_name("declarator");
14274    }
14275    let field = field_name_in_parent(declaration, child);
14276    if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
14277        || (declaration.kind() == "type_definition"
14278            && child.kind() == "type_identifier"
14279            && matches!(field, Some("declarator") | None))
14280    {
14281        Some(child)
14282    } else {
14283        None
14284    }
14285}
14286
14287/// One run of a container's children whose parsed namespaces differ from
14288/// their lexical namespaces, with the complete lexical namespace path.
14289#[derive(Clone, Debug, PartialEq, Eq)]
14290pub struct RecoveredNamespaceRegion {
14291    /// Start byte of the first child in the run.
14292    pub start: usize,
14293    /// End byte of the last child in the run.
14294    pub end: usize,
14295    /// The complete enclosing namespace path of the run, outermost first.
14296    /// This can be empty when a parsed namespace extends past its real close.
14297    pub components: Vec<String>,
14298    /// The `namespace Name { ... }` construct that opens each of `components`,
14299    /// parallel to it, from the `namespace` keyword through the matching close
14300    /// the brace stack paired with its `{`. A level whose close the stack could
14301    /// not prove -- the file never closes it, or the file's brace token stream
14302    /// lost an opening token (#3328) -- carries its head alone. This is the
14303    /// range the declaration walk records for the Module, so a namespace whose
14304    /// head collapsed into an `ERROR` reports where it is written rather than
14305    /// where its first member is (#3309).
14306    pub component_ranges: Vec<Range>,
14307}
14308
14309/// The namespaces C++ parse recovery drops from a file's tree.
14310///
14311/// When tree-sitter cannot parse a construct inside a namespace body it closes
14312/// an inner scope with a MISSING brace, or skips an opening brace into an
14313/// ERROR node. Every real `}` after that then closes one scope too early: a
14314/// class body's `}` closes the namespace, the namespace's own `}` closes its
14315/// parent, and the outermost real closes land in a trailing ERROR node. The
14316/// declarations between a stolen close and the real one keep their byte
14317/// positions but lose their `namespace_definition` ancestors (Catch2's
14318/// `catch_matchers_templated.hpp`, issue #1537).
14319///
14320/// A file-global stack over real AST brace tokens restores both lost and
14321/// overextended namespaces. Missing braces do nothing; error-free subtrees are
14322/// balanced and can be skipped. For each child of a damaged container, compare
14323/// the namespaces still open on that stack with the parsed ancestor path. A
14324/// difference yields a region carrying the complete lexical path, including
14325/// an empty path when recovery swallowed file-scope declarations. Consecutive
14326/// children sharing that path form one [`RecoveredNamespaceRegion`]. The same
14327/// pass retains matching brace positions for declaration partitioning (#3087).
14328/// A file without parse errors needs neither correction nor a brace index.
14329///
14330/// The stack restores the source's nesting only while the tree's brace tokens
14331/// are the source's braces. Severe recovery breaks that: in simdjson's
14332/// `compile_time_json-inl.h` the C++26 reflection syntax leaves the whole
14333/// translation unit an `ERROR`, and inside it `case '{':` re-lexes as a
14334/// `compound_literal_expression` whose `initializer_list` opens on the char
14335/// literal's own `{`, while the statement's real `{` is consumed as `character`
14336/// tokens of a char literal that never terminates. That file's tree holds 219
14337/// real `}` against 211 real `{`; eight closes therefore find the stack empty,
14338/// and the two namespace levels are paired with interior closes 245 lines
14339/// before their own (#3328). An unpaired close proves the stream lost an open at
14340/// a position the stack cannot recover, so the pairing of that file is not
14341/// evidence of any extent: [`Self::matching_close_brace`] then answers nothing
14342/// and each restored level keeps the head that writes it. The namespace paths
14343/// remain, as the best-effort structure recovery they are.
14344#[derive(Clone, Debug, Default)]
14345pub struct OrphanedNamespaceScopeIndex {
14346    regions: Vec<RecoveredNamespaceRegion>,
14347    /// Open-brace start byte to the close the file-global stack paired with it.
14348    /// Empty when the walk proved the brace token stream incomplete.
14349    brace_closes: HashMap<usize, Range>,
14350}
14351
14352impl OrphanedNamespaceScopeIndex {
14353    pub fn build(root: Node<'_>, source: &str) -> Self {
14354        if !root.has_error() {
14355            return Self::default();
14356        }
14357        /// One namespace level open on the brace stack: the `namespace Name {`
14358        /// head it is written as, and the `{`'s start byte, which is the
14359        /// `brace_closes` key its close lands under. The close is not known
14360        /// while the level is open, so the final ranges are assembled once the
14361        /// walk has paired every brace.
14362        #[derive(Clone, Copy)]
14363        struct OpenHead {
14364            head: Range,
14365            open: usize,
14366        }
14367        /// A [`RecoveredNamespaceRegion`] whose levels still name their open
14368        /// braces rather than their closes.
14369        struct PendingRegion {
14370            start: usize,
14371            end: usize,
14372            components: Vec<String>,
14373            heads: Vec<OpenHead>,
14374        }
14375        struct Frame<'tree> {
14376            node: Node<'tree>,
14377            children: Vec<Node<'tree>>,
14378            next: usize,
14379            parsed_scope: Vec<String>,
14380            run: Option<PendingRegion>,
14381        }
14382        fn frame<'tree>(
14383            node: Node<'tree>,
14384            mut parsed_scope: Vec<String>,
14385            source: &str,
14386        ) -> Frame<'tree> {
14387            if node.kind() == "namespace_definition"
14388                && let Some(name) = node.child_by_field_name("name")
14389            {
14390                let mut components = Vec::new();
14391                if append_cpp_name_components(name, source, &mut components).is_some() {
14392                    parsed_scope.extend(components);
14393                }
14394            }
14395            let mut cursor = node.walk();
14396            Frame {
14397                node,
14398                children: node.children(&mut cursor).collect(),
14399                next: 0,
14400                parsed_scope,
14401                run: None,
14402            }
14403        }
14404        let mut regions = Vec::new();
14405        let mut brace_closes = HashMap::default();
14406        // Real closes that arrived with nothing open. Each one proves the tree
14407        // is missing a scope-opening token the source writes, which is what
14408        // makes every pairing in the file unusable as an extent (#3328). The
14409        // complete set is kept, not a count, so a walk that has to be diagnosed
14410        // shows every position the stream went out of sync.
14411        let mut unpaired_closes: Vec<Range> = Vec::new();
14412        // The brace stack belongs to the file, not to a parser frame. A real
14413        // close inside a damaged child can close its parent's namespace. The
14414        // following children must see that removal immediately (#3087).
14415        let mut open = Vec::new();
14416        let mut lexical_scope = Vec::new();
14417        // One head per entry of `lexical_scope`: `namespace a::b {` opens two
14418        // levels and both are written by that one head, exactly as the
14419        // ordinary namespace visit records one range for every level of the
14420        // C++17 shorthand (#1878).
14421        let mut lexical_heads: Vec<OpenHead> = Vec::new();
14422        let mut frames = vec![frame(root, Vec::new(), source)];
14423        // A frame's node is the previous frame's direct child, so the frame
14424        // below answers what Node::parent would without re-descending from the
14425        // root; per-node parent or sibling climbs are quadratic over a wide
14426        // translation unit (#3141).
14427        while !frames.is_empty() {
14428            let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
14429            let current = frames.last_mut().expect("frames is non-empty");
14430            if current.next == current.children.len() {
14431                regions.extend(frames.pop().expect("the frame just borrowed").run);
14432                continue;
14433            }
14434            let child = current.children[current.next];
14435            current.next += 1;
14436            match child.kind() {
14437                "{" if !child.is_missing() => {
14438                    regions.extend(current.run.take());
14439                    // The head is the construct that writes the namespace: the
14440                    // `namespace_definition` node when one survived, and the
14441                    // bare `namespace` keyword when recovery collapsed the head
14442                    // into flat `ERROR` children (#3084).
14443                    let mut named = parent_node.and_then(|parent| {
14444                        let components =
14445                            namespace_body_name_components(parent, current.node, source);
14446                        (!components.is_empty()).then_some((parent, components))
14447                    });
14448                    if named.is_none() {
14449                        named = recovered_namespace_open_components(
14450                            &current.children[..current.next - 1],
14451                            source,
14452                        );
14453                    }
14454                    open.push((child.start_byte(), lexical_scope.len()));
14455                    if let Some((written_at, components)) = named {
14456                        let head = OpenHead {
14457                            head: Range {
14458                                start_byte: written_at.start_byte(),
14459                                end_byte: child.end_byte(),
14460                                start_line: written_at.start_position().row + 1,
14461                                end_line: child.end_position().row + 1,
14462                            },
14463                            open: child.start_byte(),
14464                        };
14465                        lexical_heads.extend(std::iter::repeat_n(head, components.len()));
14466                        lexical_scope.extend(components);
14467                    }
14468                    continue;
14469                }
14470                "}" if !child.is_missing() => {
14471                    regions.extend(current.run.take());
14472                    let close = Range {
14473                        start_byte: child.start_byte(),
14474                        end_byte: child.end_byte(),
14475                        start_line: child.start_position().row + 1,
14476                        end_line: child.end_position().row + 1,
14477                    };
14478                    match open.pop() {
14479                        Some((start, namespace_len)) => {
14480                            debug_assert!(
14481                                start < close.start_byte,
14482                                "a close pairs with an open before it: {start} then {close:?}"
14483                            );
14484                            lexical_scope.truncate(namespace_len);
14485                            lexical_heads.truncate(namespace_len);
14486                            brace_closes.insert(start, close);
14487                        }
14488                        None => unpaired_closes.push(close),
14489                    }
14490                    continue;
14491                }
14492                _ => {}
14493            }
14494            // The namespace head and body precede their opening brace; their
14495            // own parsed namespace is not yet on the lexical stack. Compare
14496            // the items inside the body, not this header bookkeeping.
14497            if current.node.kind() != "namespace_definition"
14498                && lexical_scope != current.parsed_scope
14499            {
14500                match &mut current.run {
14501                    Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14502                    run => {
14503                        regions.extend(run.take());
14504                        *run = Some(PendingRegion {
14505                            start: child.start_byte(),
14506                            end: child.end_byte(),
14507                            components: lexical_scope.clone(),
14508                            heads: lexical_heads.clone(),
14509                        });
14510                    }
14511                }
14512            } else {
14513                regions.extend(current.run.take());
14514            }
14515            // Error-free subtrees are balanced. Keeping them intact also
14516            // leaves their valid nested namespaces to the ordinary ancestry
14517            // walk in restore_enclosing_namespaces.
14518            if child.has_error() {
14519                let parsed_scope = current.parsed_scope.clone();
14520                frames.push(frame(child, parsed_scope, source));
14521            }
14522        }
14523        // A close with nothing open is only possible when a `{` the source
14524        // writes is not a `{` token of the tree. The stack then paired some
14525        // earlier open with a close that belongs to an inner scope, and
14526        // nothing in the token stream says which pairs absorbed the loss: the
14527        // lost token sits at an unknown position before the close that
14528        // exposed it. Keep no pairing rather than an unprovable extent. An
14529        // open the file never closes is the opposite case and stays: it makes
14530        // no claim, so the levels it writes keep their heads either way.
14531        if !unpaired_closes.is_empty() {
14532            brace_closes.clear();
14533        }
14534        // Every surviving brace is paired now, so each level's head can grow to
14535        // the construct it writes. A level whose `{` never closes keeps its
14536        // head: the file ends inside the namespace and there is no real close
14537        // to reach for.
14538        let regions = regions
14539            .into_iter()
14540            .map(|region| {
14541                debug_assert_eq!(
14542                    region.components.len(),
14543                    region.heads.len(),
14544                    "every recovered namespace level carries the head that writes it: {:?}",
14545                    region.components
14546                );
14547                RecoveredNamespaceRegion {
14548                    start: region.start,
14549                    end: region.end,
14550                    component_ranges: region
14551                        .heads
14552                        .iter()
14553                        .map(|level| match brace_closes.get(&level.open) {
14554                            Some(close) => Range {
14555                                start_byte: level.head.start_byte,
14556                                end_byte: close.end_byte,
14557                                start_line: level.head.start_line,
14558                                end_line: close.end_line,
14559                            },
14560                            None => level.head,
14561                        })
14562                        .collect(),
14563                    components: region.components,
14564                }
14565            })
14566            .collect();
14567        Self {
14568            regions,
14569            brace_closes,
14570        }
14571    }
14572
14573    /// The real AST brace matching an opening brace in a damaged subtree.
14574    /// Missing tokens and braces inside comments or literals do not participate.
14575    ///
14576    /// `None` when this open has no close in the file, and for every open in a
14577    /// file whose brace token stream lost an opening token: a caller cannot
14578    /// treat a pairing the walk could not prove as an enclosing extent (#3328).
14579    pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14580        self.brace_closes.get(&open).copied()
14581    }
14582
14583    pub fn is_empty(&self) -> bool {
14584        self.regions.is_empty()
14585    }
14586
14587    /// The bytes this index holds, for the analyzer cache's weight.
14588    pub fn approximate_size(&self) -> usize {
14589        self.regions.iter().fold(
14590            self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14591            |total, region| {
14592                total
14593                    .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14594                    .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14595                    .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
14596            },
14597        )
14598    }
14599
14600    /// The innermost recovered region containing `byte`.
14601    pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14602        self.regions
14603            .iter()
14604            .filter(|region| region.start <= byte && byte < region.end)
14605            .min_by_key(|region| region.end - region.start)
14606    }
14607
14608    /// The enclosing namespaces of `node`, outermost first, restoring the ones
14609    /// parse recovery dropped from its ancestor chain. The one answer both
14610    /// lookup directions and declaration collection use (issue #1537).
14611    pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14612        self.enclosing_namespace_levels(node, source)
14613            .into_iter()
14614            .map(|(component, _)| component)
14615            .collect()
14616    }
14617
14618    /// [`Self::enclosing_namespace_components`] with the construct that writes
14619    /// each level, for the declaration walk that has to record a range for it.
14620    ///
14621    /// A level the brace stack restored carries the range this index recorded
14622    /// for its head; a surviving `namespace_definition` ancestor carries its own
14623    /// node range, which is what the ordinary namespace visit records.
14624    pub fn enclosing_namespace_levels(&self, node: Node<'_>, source: &str) -> Vec<(String, Range)> {
14625        let mut parsed = Vec::new();
14626        let mut current = node.parent();
14627        while let Some(parent) = current {
14628            if parent.kind() == "namespace_definition"
14629                && let Some(name) = parent.child_by_field_name("name")
14630            {
14631                let mut components = Vec::new();
14632                if append_cpp_name_components(name, source, &mut components).is_some() {
14633                    let range = node_range(parent);
14634                    parsed.push((
14635                        parent.start_byte(),
14636                        components
14637                            .into_iter()
14638                            .map(|component| (component, range))
14639                            .collect(),
14640                    ));
14641                }
14642            }
14643            current = parent.parent();
14644        }
14645        parsed.reverse();
14646        self.splice_recovered_path(parsed, node.start_byte(), |region| {
14647            region
14648                .components
14649                .iter()
14650                .cloned()
14651                .zip(region.component_ranges.iter().copied())
14652                .collect()
14653        })
14654    }
14655
14656    /// [`Self::enclosing_namespace_components`] for a caller that has already
14657    /// climbed the ancestor chain: `parsed` lists the node's named
14658    /// `namespace_definition` ancestors outermost first, each with its start
14659    /// byte. A region covering the node supplies every namespace outside it;
14660    /// only the parsed ancestors that start inside the region still apply.
14661    pub fn restore_enclosing_namespaces(
14662        &self,
14663        parsed: Vec<(usize, Vec<String>)>,
14664        node_start: usize,
14665    ) -> Vec<String> {
14666        self.splice_recovered_path(parsed, node_start, |region| region.components.clone())
14667    }
14668
14669    /// Splice a node's parsed namespace ancestors onto the path a recovered
14670    /// region restores. Outside every region the parsed chain is the whole
14671    /// answer; inside one, the region supplies every namespace enclosing it and
14672    /// only the parsed ancestors beginning inside it still apply. `recovered`
14673    /// reads the region in whatever shape the caller needs its levels.
14674    fn splice_recovered_path<T>(
14675        &self,
14676        parsed: Vec<(usize, Vec<T>)>,
14677        node_start: usize,
14678        recovered: impl FnOnce(&RecoveredNamespaceRegion) -> Vec<T>,
14679    ) -> Vec<T> {
14680        let Some(region) = self.region_at(node_start) else {
14681            return parsed.into_iter().flat_map(|(_, levels)| levels).collect();
14682        };
14683        recovered(region)
14684            .into_iter()
14685            .chain(
14686                parsed
14687                    .into_iter()
14688                    .filter(|(start, _)| *start >= region.start)
14689                    .flat_map(|(_, levels)| levels),
14690            )
14691            .collect()
14692    }
14693}
14694
14695/// The `namespace` keyword and name components of the namespace a stray `{`
14696/// opens, or `None` when the brace does not follow a namespace head.
14697///
14698/// When a namespace body holds a construct tree-sitter cannot parse, recovery
14699/// can collapse the whole `namespace Name { ... }` into one `ERROR` instead of
14700/// a `namespace_definition`: the head survives as the node's own `namespace`
14701/// keyword, name and `{` tokens, in that order, and everything the namespace
14702/// declares becomes a flat sibling of them (Catch2's `catch_decomposer.hpp`,
14703/// issue #3084). Read the head from those siblings so the brace stack names
14704/// the scope the brace opens. An anonymous namespace has no representable
14705/// name and keeps an opaque scope.
14706///
14707/// `preceding` is the open brace's preceding siblings, nearest last. The
14708/// caller already holds the container's child list; `Node::prev_sibling`
14709/// would re-descend from the root for each step (#3141).
14710fn recovered_namespace_open_components<'tree>(
14711    preceding: &[Node<'tree>],
14712    source: &str,
14713) -> Option<(Node<'tree>, Vec<String>)> {
14714    let mut head = Vec::new();
14715    for &sibling in preceding.iter().rev() {
14716        if sibling.kind() != "comment" {
14717            head.push(sibling);
14718            if head.len() == 2 {
14719                break;
14720            }
14721        }
14722    }
14723    let [name, keyword] = head[..] else {
14724        return None;
14725    };
14726    if keyword.kind() != "namespace" {
14727        return None;
14728    }
14729    let mut components = Vec::new();
14730    append_cpp_name_components(name, source, &mut components)?;
14731    (!components.is_empty()).then_some((keyword, components))
14732}
14733
14734/// The name components of the namespace whose body `body` is, or empty when
14735/// `body` is not the body of a named `namespace_definition` `parent`.
14736fn namespace_body_name_components(parent: Node<'_>, body: Node<'_>, source: &str) -> Vec<String> {
14737    let mut components = Vec::new();
14738    if body.kind() == "declaration_list"
14739        && parent.kind() == "namespace_definition"
14740        && parent.child_by_field_name("body") == Some(body)
14741        && let Some(name) = parent.child_by_field_name("name")
14742        && append_cpp_name_components(name, source, &mut components).is_none()
14743    {
14744        components.clear();
14745    }
14746    components
14747}
14748
14749#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14750pub enum RecoveredDeclaratorTypeContext {
14751    Declaration,
14752    FunctionDefinition,
14753    Parameter,
14754}
14755
14756/// Recognize a real type displaced into a qualified declarator by parser
14757/// recovery.
14758///
14759/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
14760/// type and `Result` were the scope of a qualified declarator with a missing
14761/// `::`. A template return such as `API Result<T> make()` uses a
14762/// `template_type` for the same recovered scope. The same recovery occurs for
14763/// macro-prefixed definitions, extern variables, and macro-decorated
14764/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
14765/// the macro). Keep this intentionally structural: the recovered scope must
14766/// have the grammar's missing separator, the qualified node must occupy the
14767/// declaration's declarator chain, a separate nonempty type must occupy the
14768/// normal type field, and the recovered name must unwrap to a real declarator
14769/// name.
14770pub fn recovered_macro_decorated_declarator_type(
14771    node: Node<'_>,
14772) -> Option<RecoveredDeclaratorTypeContext> {
14773    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
14774}
14775
14776/// Return the declaration/function `type` displaced by a macro-shaped
14777/// qualified declarator, together with the enclosing declaration context.
14778/// Callers use the macro scope only as structural admission evidence; the
14779/// returned node is the real type reference to resolve and record.
14780pub fn recovered_macro_decorated_type_node(
14781    node: Node<'_>,
14782) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14783    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
14784        return None;
14785    }
14786    let qualified = node.parent()?;
14787    if qualified.kind() != "qualified_identifier"
14788        || qualified.child_by_field_name("scope") != Some(node)
14789        || !(0..qualified.child_count())
14790            .filter_map(|index| qualified.child(index))
14791            .any(|child| child.kind() == "::" && child.is_missing())
14792    {
14793        return None;
14794    }
14795    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
14796        return None;
14797    }
14798
14799    let (declaration, context) = recovered_declarator_container(qualified)?;
14800    let type_node = declaration
14801        .child_by_field_name("type")
14802        .filter(|type_node| {
14803            *type_node != qualified
14804                && !type_node.is_missing()
14805                && type_node.start_byte() != type_node.end_byte()
14806        })?;
14807    Some((type_node, context))
14808}
14809
14810fn recovered_declarator_container(
14811    mut declarator: Node<'_>,
14812) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14813    loop {
14814        let parent = declarator.parent()?;
14815        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
14816            return Some((
14817                parent
14818                    .parent()
14819                    .filter(|declaration| declaration.kind() == "declaration")?,
14820                RecoveredDeclaratorTypeContext::Declaration,
14821            ));
14822        }
14823        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
14824            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
14825        }
14826        if parent.kind() == "function_definition"
14827            && has_field_child(parent, "declarator", declarator)
14828        {
14829            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
14830        }
14831        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
14832        // level down: the parameter's `type` field takes the macro token and
14833        // the real type `T` becomes the recovered scope of the declarator.
14834        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
14835        // candidate at all (#1830).
14836        if matches!(
14837            parent.kind(),
14838            "parameter_declaration" | "optional_parameter_declaration"
14839        ) && has_field_child(parent, "declarator", declarator)
14840        {
14841            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
14842        }
14843        if !matches!(
14844            parent.kind(),
14845            "array_declarator"
14846                | "function_declarator"
14847                | "parenthesized_declarator"
14848                | "pointer_declarator"
14849                | "pointer_type_declarator"
14850                | "reference_declarator"
14851        ) || !has_field_child(parent, "declarator", declarator)
14852        {
14853            return None;
14854        }
14855        declarator = parent;
14856    }
14857}
14858
14859fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
14860    let mut cursor = parent.walk();
14861    parent
14862        .children_by_field_name(field, &mut cursor)
14863        .any(|child| child == target)
14864}
14865
14866fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
14867    loop {
14868        if node.is_missing() || node.start_byte() == node.end_byte() {
14869            return false;
14870        }
14871        match node.kind() {
14872            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
14873                return true;
14874            }
14875            "array_declarator"
14876            | "function_declarator"
14877            | "parenthesized_declarator"
14878            | "pointer_declarator"
14879            | "pointer_type_declarator"
14880            | "reference_declarator" => {
14881                let Some(declarator) = node.child_by_field_name("declarator") else {
14882                    return false;
14883                };
14884                node = declarator;
14885            }
14886            _ => return false,
14887        }
14888    }
14889}
14890
14891/// Aggregate-owner proof for a structurally recognized designated initializer.
14892pub enum DesignatedInitializerOwner {
14893    Resolved(CodeUnit),
14894    Unresolved,
14895}
14896
14897enum InitializerOwnerStep {
14898    Field(String),
14899    AggregateWrapper,
14900}
14901
14902/// Recognize a designated-initializer field and, when possible, resolve its
14903/// aggregate owner.
14904///
14905/// Covers both the grammar's ordinary `field_designator` shape and the exact
14906/// recovery used for `.field = value` after a preprocessor-split array
14907/// initializer. Ordered multi-component designators follow each preceding
14908/// field's declared aggregate type. `None` means the node is not a designator
14909/// at all; an unresolved designator remains classified so callers cannot fall
14910/// through to unrelated global/member heuristics.
14911pub fn designated_initializer_owner(
14912    analyzer: &CppGraphSource<'_>,
14913    visibility: &VisibilityIndex<'_>,
14914    file: &ProjectFile,
14915    source: &str,
14916    node: Node<'_>,
14917) -> Option<DesignatedInitializerOwner> {
14918    if let Some(designator) = node
14919        .parent()
14920        .filter(|parent| parent.kind() == "field_designator")
14921    {
14922        let pair = designator.parent()?;
14923        if pair.kind() != "initializer_pair" {
14924            return None;
14925        }
14926        let mut cursor = pair.walk();
14927        let designators = pair
14928            .children_by_field_name("designator", &mut cursor)
14929            .collect::<Vec<_>>();
14930        let position = designators
14931            .iter()
14932            .position(|candidate| same_node(*candidate, designator))?;
14933        let initializer = pair.parent()?;
14934        if initializer.kind() != "initializer_list" {
14935            return None;
14936        }
14937        let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
14938        for prior in &designators[..position] {
14939            let field = prior
14940                .child_by_field_name("field")
14941                .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
14942            owner = owner.and_then(|owner| {
14943                initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
14944            });
14945        }
14946        return Some(classified_designated_owner(owner));
14947    }
14948
14949    let init_declarator = node.parent()?;
14950    if init_declarator.child_by_field_name("declarator") != Some(node)
14951        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
14952    {
14953        return None;
14954    }
14955    Some(classified_designated_owner(declaration_owner(
14956        analyzer,
14957        visibility,
14958        file,
14959        source,
14960        init_declarator.parent()?,
14961    )))
14962}
14963
14964fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
14965    owner.map_or(
14966        DesignatedInitializerOwner::Unresolved,
14967        DesignatedInitializerOwner::Resolved,
14968    )
14969}
14970
14971fn initializer_list_owner(
14972    analyzer: &CppGraphSource<'_>,
14973    visibility: &VisibilityIndex<'_>,
14974    file: &ProjectFile,
14975    source: &str,
14976    initializer: Node<'_>,
14977) -> Option<CodeUnit> {
14978    let mut current = initializer;
14979    let mut steps = Vec::new();
14980    loop {
14981        let parent = current.parent()?;
14982        match parent.kind() {
14983            "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
14984                let designator = parent.child_by_field_name("designator")?;
14985                let step = designator
14986                    .child_by_field_name("field")
14987                    .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
14988                    .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
14989                    .unwrap_or(InitializerOwnerStep::AggregateWrapper);
14990                steps.push(step);
14991                current = parent.parent()?;
14992            }
14993            "initializer_list" => {
14994                current = parent;
14995            }
14996            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
14997                let declaration = parent.parent()?;
14998                let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
14999                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15000            }
15001            "compound_literal_expression"
15002                if parent.child_by_field_name("value") == Some(current) =>
15003            {
15004                let type_node = parent.child_by_field_name("type")?;
15005                let owner =
15006                    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
15007                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15008            }
15009            "ERROR" => current = parent,
15010            _ => return None,
15011        }
15012    }
15013}
15014
15015fn apply_initializer_owner_steps(
15016    analyzer: &CppGraphSource<'_>,
15017    visibility: &VisibilityIndex<'_>,
15018    file: &ProjectFile,
15019    mut owner: CodeUnit,
15020    steps: Vec<InitializerOwnerStep>,
15021) -> Option<CodeUnit> {
15022    for step in steps.into_iter().rev() {
15023        if let InitializerOwnerStep::Field(field_name) = step {
15024            owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
15025        }
15026    }
15027    Some(owner)
15028}
15029
15030fn initializer_field_owner(
15031    analyzer: &CppGraphSource<'_>,
15032    visibility: &VisibilityIndex<'_>,
15033    file: &ProjectFile,
15034    owner: CodeUnit,
15035    field_name: &str,
15036) -> Option<CodeUnit> {
15037    let fields = visibility
15038        .visible_members_for_owner_name(file, &owner, field_name)
15039        .into_iter()
15040        .filter(|field| field.is_field())
15041        .collect::<Vec<_>>();
15042    let field = match fields.as_slice() {
15043        [field] => *field,
15044        _ => return None,
15045    };
15046    field_declared_binding(analyzer, visibility, file, field)?.unit
15047}
15048
15049fn declaration_owner(
15050    analyzer: &CppGraphSource<'_>,
15051    visibility: &VisibilityIndex<'_>,
15052    file: &ProjectFile,
15053    source: &str,
15054    declaration: Node<'_>,
15055) -> Option<CodeUnit> {
15056    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
15057        return None;
15058    }
15059    let type_node = declaration
15060        .child_by_field_name("type")
15061        .or_else(|| first_type_child(declaration))?;
15062    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
15063}
15064
15065fn resolve_designated_owner_type(
15066    analyzer: &CppGraphSource<'_>,
15067    visibility: &VisibilityIndex<'_>,
15068    file: &ProjectFile,
15069    source: &str,
15070    type_node: Node<'_>,
15071) -> Option<CodeUnit> {
15072    if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
15073        return Some(owner);
15074    }
15075    let type_name = normalize_type_text(node_text(type_node, source));
15076    visibility
15077        .resolve_type(file, &type_name)
15078        .filter(CodeUnit::is_class)
15079}
15080
15081pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
15082    let mut cursor = node.walk();
15083    node.named_children(&mut cursor).find(|child| {
15084        matches!(
15085            child.kind(),
15086            "type_identifier"
15087                | "primitive_type"
15088                | "qualified_identifier"
15089                | "scoped_type_identifier"
15090                | "struct_specifier"
15091                | "union_specifier"
15092                | "enum_specifier"
15093        )
15094    })
15095}
15096
15097pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
15098    visibility: &VisibilityIndex<'_>,
15099    file: &ProjectFile,
15100    source: &str,
15101    declarator: Node<'_>,
15102    type_text: Option<&str>,
15103    bindings: &LocalInferenceEngine<T>,
15104) -> bool {
15105    if !has_ancestor_kind(declarator, "compound_statement") {
15106        return false;
15107    }
15108    if declarator
15109        .child_by_field_name("declarator")
15110        .is_none_or(|declarator| declarator.kind() != "identifier")
15111    {
15112        return false;
15113    }
15114    if !type_text
15115        .and_then(|text| visibility.resolve_type(file, text))
15116        .is_some_and(|unit| unit.is_class())
15117    {
15118        return false;
15119    }
15120    declarator
15121        .child_by_field_name("parameters")
15122        .is_some_and(|parameters| {
15123            constructor_parameters_look_like_expressions(parameters, source, bindings)
15124        })
15125}
15126
15127fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
15128    parameters: Node<'_>,
15129    source: &str,
15130    bindings: &LocalInferenceEngine<T>,
15131) -> bool {
15132    let mut cursor = parameters.walk();
15133    parameters.named_children(&mut cursor).any(|parameter| {
15134        !matches!(
15135            parameter.kind(),
15136            "parameter_declaration" | "optional_parameter_declaration"
15137        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
15138    })
15139}
15140
15141fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
15142    parameter: Node<'_>,
15143    source: &str,
15144    bindings: &LocalInferenceEngine<T>,
15145) -> bool {
15146    let text = node_text(parameter, source).trim();
15147    if text
15148        .chars()
15149        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
15150        && bindings.is_shadowed(text)
15151    {
15152        return true;
15153    }
15154
15155    let Some(base) = parameter
15156        .child_by_field_name("type")
15157        .filter(|base| base.kind() == "type_identifier")
15158    else {
15159        return false;
15160    };
15161    let Some(subscript) = parameter
15162        .child_by_field_name("declarator")
15163        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
15164    else {
15165        return false;
15166    };
15167    subscript.child_by_field_name("size").is_some()
15168        && bindings.is_shadowed(node_text(base, source).trim())
15169}
15170
15171pub fn is_declaration_name(node: Node<'_>) -> bool {
15172    let Some(parent) = node.parent() else {
15173        return false;
15174    };
15175    if parent
15176        .child_by_field_name("name")
15177        .is_some_and(|name| same_node(name, node))
15178    {
15179        if matches!(
15180            parent.kind(),
15181            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
15182        ) {
15183            return cpp_tag_specifier_declares_name(parent);
15184        }
15185        if matches!(
15186            parent.kind(),
15187            "namespace_definition"
15188                | "namespace_alias_definition"
15189                | "alias_declaration"
15190                | "enumerator"
15191        ) {
15192            return true;
15193        }
15194    }
15195
15196    let mut current = Some(parent);
15197    while let Some(ancestor) = current {
15198        let type_definition = ancestor.kind() == "type_definition";
15199        let mut child_cursor = ancestor.walk();
15200        if ancestor.named_children(&mut child_cursor).any(|child| {
15201            declaration_declarator(ancestor, child).is_some_and(|declarator| {
15202                declarator_name_path_contains(declarator, node, type_definition)
15203            })
15204        }) {
15205            return true;
15206        }
15207        if matches!(
15208            ancestor.kind(),
15209            "declaration"
15210                | "field_declaration"
15211                | "parameter_declaration"
15212                | "optional_parameter_declaration"
15213                | "function_definition"
15214                | "type_definition"
15215                | "alias_declaration"
15216                | "template_instantiation"
15217                | "class_specifier"
15218                | "struct_specifier"
15219                | "union_specifier"
15220                | "enum_specifier"
15221        ) {
15222            return false;
15223        }
15224        current = ancestor.parent();
15225    }
15226    false
15227}
15228
15229/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
15230/// declaration's declarator inside a malformed class body.
15231///
15232/// An export macro between `class` and the class name can make the containing
15233/// body parse as a function body. A source declaration such as
15234/// `friend class internal::Friend;` then retains this exact structure:
15235/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
15236/// The declarator is a type reference despite its field role.
15237pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
15238    if !matches!(
15239        node.kind(),
15240        "qualified_identifier" | "scoped_type_identifier"
15241    ) {
15242        return false;
15243    }
15244    let Some(declaration) = node
15245        .parent()
15246        .filter(|parent| parent.kind() == "declaration")
15247    else {
15248        return false;
15249    };
15250    if declaration.child_by_field_name("declarator") != Some(node)
15251        || !declaration
15252            .child_by_field_name("type")
15253            .is_some_and(|friend| {
15254                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
15255            })
15256    {
15257        return false;
15258    }
15259    let mut cursor = declaration.walk();
15260    let mut errors = declaration
15261        .named_children(&mut cursor)
15262        .filter(|child| child.kind() == "ERROR");
15263    let Some(error) = errors.next() else {
15264        return false;
15265    };
15266    errors.next().is_none()
15267        && error.named_child_count() == 1
15268        && error.named_child(0).is_some_and(|class| {
15269            class.kind() == "identifier" && node_text(class, source) == "class"
15270        })
15271}
15272
15273pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
15274    if !matches!(node.kind(), "identifier" | "field_identifier") {
15275        return false;
15276    }
15277    if let Some(parent) = node.parent() {
15278        if parent.kind() == "call_expression"
15279            && parent.child_by_field_name("function") == Some(node)
15280        {
15281            return false;
15282        }
15283        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
15284            && parent.child_by_field_name("label") == Some(node)
15285        {
15286            return false;
15287        }
15288    }
15289    if is_declaration_name(node) {
15290        return false;
15291    }
15292    let mut current = node.parent();
15293    while let Some(ancestor) = current {
15294        match ancestor.kind() {
15295            "preproc_ifdef" | "preproc_ifndef" => {
15296                if ancestor
15297                    .child_by_field_name("name")
15298                    .is_some_and(|name| node_range_contains(name, node))
15299                {
15300                    return false;
15301                }
15302            }
15303            "preproc_if" | "preproc_elif" => {
15304                if ancestor
15305                    .child_by_field_name("condition")
15306                    .is_some_and(|condition| node_range_contains(condition, node))
15307                {
15308                    return false;
15309                }
15310            }
15311            "preproc_else" => {}
15312            kind if kind.starts_with("preproc_") => return false,
15313            _ => {}
15314        }
15315        if matches!(
15316            ancestor.kind(),
15317            "translation_unit" | "function_definition" | "compound_statement"
15318        ) {
15319            break;
15320        }
15321        current = ancestor.parent();
15322    }
15323    true
15324}
15325
15326fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
15327    outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
15328}
15329
15330fn recovered_c_reference_node(
15331    visibility: &VisibilityIndex<'_>,
15332    file: &ProjectFile,
15333    node: Node<'_>,
15334    source: &str,
15335) -> bool {
15336    if node.start_byte() >= node.end_byte()
15337        || node.is_error()
15338        || node.is_missing()
15339        || !matches!(
15340            node.kind(),
15341            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
15342        )
15343        || recovered_c_macro_binding_role(node)
15344        || recovered_c_label_role(node)
15345    {
15346        return false;
15347    }
15348    // A declaration name can be an identifier child of a recovered ERROR
15349    // (for example `EFI_STATUS Encode()` in C files parsed as C++). Do not
15350    // let a same-named macro turn that binder into a reference. An
15351    // assignment whose C callee follows the recovered `explicit` token is
15352    // the one expression-shaped exception: the generic declarator walk sees
15353    // its function_declarator as a declaration path, but the ERROR sibling
15354    // proves it is a call.
15355    let name = node_text(node, source);
15356    let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
15357    let recovered_macro_call = recovered_c_function_declarator_invocation(node)
15358        && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
15359    let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
15360    if is_declaration_name(node)
15361        && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
15362        && !recovered_function_call
15363        && !recovered_macro_call
15364        && !recovered_parenthesized_reference
15365    {
15366        return false;
15367    }
15368
15369    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
15370        return true;
15371    }
15372    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
15373        return true;
15374    }
15375    if recovered_parenthesized_reference {
15376        return true;
15377    }
15378    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
15379        if recovered_function_call {
15380            return true;
15381        }
15382        return visibility
15383            .visible_identifier_candidates(file, name)
15384            .any(|candidate| {
15385                candidate.is_class() || candidate.is_module() || is_type_alias(candidate)
15386            });
15387    }
15388    let visible = visibility
15389        .visible_identifier_candidates(file, name)
15390        .next()
15391        .is_some();
15392    visible
15393        && (recovered_c_reference_anchor(node)
15394            || recovered_c_error_expression_leaf(node)
15395            || recovered_function_call)
15396}
15397
15398fn push_recovered_c_range(
15399    ranges: &mut Vec<Range>,
15400    seen: &mut HashSet<(usize, usize)>,
15401    start_byte: usize,
15402    end_byte: usize,
15403    node: Node<'_>,
15404    limit: usize,
15405) -> bool {
15406    if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
15407        return true;
15408    }
15409    if ranges.len() >= limit {
15410        return false;
15411    }
15412    ranges.push(Range {
15413        start_byte,
15414        end_byte,
15415        start_line: node.start_position().row,
15416        end_line: node.end_position().row,
15417    });
15418    true
15419}
15420
15421/// A reference leaf can sit directly beneath an ERROR while its ERROR parent
15422/// is still attached to a real expression (most often a recovered macro call
15423/// argument). The expression parent is the structured proof; an unindexed
15424/// identifier beneath a bare recovery envelope has no such proof.
15425fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
15426    let mut current = node.parent();
15427    while let Some(parent) = current {
15428        if parent.is_error() {
15429            let Some(anchor) = parent.parent() else {
15430                return false;
15431            };
15432            return anchor.kind().ends_with("_expression")
15433                || matches!(
15434                    anchor.kind(),
15435                    "argument_list"
15436                        | "return_statement"
15437                        | "expression_statement"
15438                        | "case_statement"
15439                        | "initializer_list"
15440                        | "field_designator"
15441                        | "enumerator"
15442                );
15443        }
15444        if matches!(
15445            parent.kind(),
15446            "translation_unit" | "function_definition" | "compound_statement"
15447        ) {
15448            return false;
15449        }
15450        current = parent.parent();
15451    }
15452    false
15453}
15454
15455/// C recovery may represent a call as `identifier > function_declarator >
15456/// function_declarator > ERROR > compound_statement`. This shape is only a
15457/// call when the malformed declarator is attached to a real function body and
15458/// the name is an indexed visible callable. A declaration's
15459/// `ERROR > function_declarator > declaration` shape deliberately fails this
15460/// test.
15461fn recovered_c_function_call(
15462    visibility: &VisibilityIndex<'_>,
15463    file: &ProjectFile,
15464    node: Node<'_>,
15465    name: &str,
15466) -> bool {
15467    if !matches!(
15468        node.kind(),
15469        "identifier" | "field_identifier" | "type_identifier"
15470    ) {
15471        return false;
15472    }
15473    // C++ keywords used as C arguments can leave only the call prefix under
15474    // ERROR, with the remaining arguments in a following expression statement.
15475    // Require a statement boundary in a real block, an opening parenthesis,
15476    // and a recovered argument. A bare ERROR identifier supplies no call role.
15477    let error_call_prefix = node.parent().is_some_and(|error| {
15478        error.is_error()
15479            && error
15480                .parent()
15481                .is_some_and(|parent| parent.kind() == "compound_statement")
15482    }) && node
15483        .prev_sibling()
15484        .is_none_or(|previous| previous.kind() == ";")
15485        && node.next_sibling().is_some_and(|open| {
15486            open.kind() == "("
15487                && open.next_named_sibling().is_some_and(|argument| {
15488                    argument.kind() == "parameter_declaration" && !argument.has_error()
15489                })
15490        });
15491    (error_call_prefix || recovered_c_function_declarator_invocation(node))
15492        && visibility
15493            .visible_identifier_candidates(file, name)
15494            .any(CodeUnit::is_function)
15495}
15496
15497/// Return whether a C identifier belongs to a call-shaped declarator that the
15498/// C++ grammar put under an `ERROR` node.
15499///
15500/// The malformed call can be direct (`f(arg)`) or nested in a parameter
15501/// declaration when one of its arguments looks like a type (`f(TYPE, value)`).
15502/// In both cases the CST retains the function-declarator and its enclosing
15503/// recovery envelope. We walk only those declarator/parameter nodes and stop
15504/// at a real expression-bearing boundary; declarations therefore cannot pass
15505/// this predicate merely because they have a parameter list.
15506fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
15507    let mut function_declarator = if node.parent().is_some_and(|parent| {
15508        parent.kind() == "function_declarator"
15509            && parent.child_by_field_name("declarator") == Some(node)
15510    }) {
15511        node.parent().expect("checked function declarator parent")
15512    } else {
15513        let Some(parameter) = node.parent().filter(|parent| {
15514            parent.kind() == "parameter_declaration"
15515                && parent.child_by_field_name("type") == Some(node)
15516        }) else {
15517            return false;
15518        };
15519        if !parameter
15520            .child_by_field_name("declarator")
15521            .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15522        {
15523            return false;
15524        }
15525        let Some(parameters) = parameter
15526            .parent()
15527            .filter(|parent| parent.kind() == "parameter_list")
15528        else {
15529            return false;
15530        };
15531        let Some(function_declarator) = parameters
15532            .parent()
15533            .filter(|parent| parent.kind() == "function_declarator")
15534        else {
15535            return false;
15536        };
15537        function_declarator
15538    };
15539
15540    // Recovery may absorb the next statement's parenthesized cast into a
15541    // second function declarator. Follow only the original declarator chain.
15542    while let Some(parent) = function_declarator.parent().filter(|parent| {
15543        parent.kind() == "function_declarator"
15544            && parent.child_by_field_name("declarator") == Some(function_declarator)
15545    }) {
15546        function_declarator = parent;
15547    }
15548    let Some(mut current) = function_declarator
15549        .parent()
15550        .filter(|parent| parent.is_error())
15551    else {
15552        return false;
15553    };
15554    loop {
15555        let Some(parent) = current.parent() else {
15556            return false;
15557        };
15558        if matches!(
15559            parent.kind(),
15560            "translation_unit"
15561                | "compound_statement"
15562                | "preproc_if"
15563                | "preproc_ifdef"
15564                | "preproc_ifndef"
15565                | "preproc_else"
15566                | "preproc_elif"
15567        ) {
15568            return true;
15569        }
15570        if parent.kind() == "function_definition"
15571            && parent.child_by_field_name("declarator") == Some(current)
15572            && parent.named_child(0) == Some(current)
15573            && parent.child_by_field_name("body").is_some()
15574        {
15575            return true;
15576        }
15577        if parent.is_error()
15578            || matches!(
15579                parent.kind(),
15580                "parameter_declaration"
15581                    | "parameter_list"
15582                    | "function_declarator"
15583                    | "abstract_function_declarator"
15584                    | "parenthesized_declarator"
15585            )
15586        {
15587            current = parent;
15588            continue;
15589        }
15590        return false;
15591    }
15592}
15593
15594/// C permits an identifier named `typename`. The C++ grammar can recover an
15595/// assignment using that identifier as a declaration whose declarator is a
15596/// parenthesized argument list, for example `typename = f(ctx, value)`. Only
15597/// the argument retained beneath the nested `ERROR` is a reference; sibling
15598/// declarator identifiers remain binders/grammar artifacts.
15599fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
15600    let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
15601        return false;
15602    };
15603    if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
15604        return false;
15605    }
15606    let Some(declarator) = error
15607        .parent()
15608        .filter(|parent| parent.kind() == "parenthesized_declarator")
15609    else {
15610        return false;
15611    };
15612    let Some(declaration) = declarator
15613        .parent()
15614        .filter(|parent| parent.kind() == "declaration")
15615    else {
15616        return false;
15617    };
15618    if declaration.child_by_field_name("declarator") != Some(declarator) {
15619        return false;
15620    }
15621    let Some(type_node) = declaration.child_by_field_name("type") else {
15622        return false;
15623    };
15624    type_node.kind() == "dependent_type"
15625        && type_node
15626            .child(0)
15627            .is_some_and(|keyword| keyword.kind() == "typename")
15628}
15629
15630fn recovered_c_explicit_assignment_callee(
15631    visibility: &VisibilityIndex<'_>,
15632    file: &ProjectFile,
15633    node: Node<'_>,
15634    name: &str,
15635) -> bool {
15636    let mut current = node;
15637    let error = loop {
15638        let Some(parent) = current.parent() else {
15639            return false;
15640        };
15641        if parent.is_error() {
15642            break parent;
15643        }
15644        current = parent;
15645    };
15646    let mut cursor = error.walk();
15647    let explicit_recovery_precedes_callee = error
15648        .named_children(&mut cursor)
15649        .take_while(|child| child.start_byte() < node.start_byte())
15650        .any(|child| child.kind() == "explicit_function_specifier");
15651    if !explicit_recovery_precedes_callee {
15652        return false;
15653    }
15654    visibility
15655        .visible_identifier_candidates(file, name)
15656        .any(CodeUnit::is_function)
15657}
15658
15659fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
15660    while let Some(parent) = node.parent() {
15661        if matches!(
15662            parent.kind(),
15663            "preproc_def" | "preproc_function_def" | "preproc_params"
15664        ) {
15665            return true;
15666        }
15667        if parent.is_error()
15668            || matches!(
15669                parent.kind(),
15670                "translation_unit" | "function_definition" | "compound_statement"
15671            )
15672        {
15673            return false;
15674        }
15675        node = parent;
15676    }
15677    false
15678}
15679
15680fn recovered_c_label_role(node: Node<'_>) -> bool {
15681    node.parent().is_some_and(|parent| {
15682        matches!(parent.kind(), "labeled_statement" | "goto_statement")
15683            && parent.child_by_field_name("label") == Some(node)
15684    })
15685}
15686
15687fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
15688    while let Some(parent) = node.parent() {
15689        if parent.is_error() {
15690            return false;
15691        }
15692        // A C macro call recovered as a function declarator can parse an
15693        // assignment-shaped argument as an optional parameter. Its
15694        // `default_value` field remains an expression role even though the
15695        // surrounding call shape is beneath ERROR.
15696        if parent.kind() == "optional_parameter_declaration"
15697            && parent
15698                .child_by_field_name("default_value")
15699                .is_some_and(|value| node_range_contains(value, node))
15700        {
15701            return true;
15702        }
15703        if parent.kind().ends_with("_expression")
15704            || matches!(
15705                parent.kind(),
15706                "argument_list"
15707                    | "return_statement"
15708                    | "expression_statement"
15709                    | "case_statement"
15710                    | "initializer_list"
15711                    | "init_declarator"
15712                    | "array_declarator"
15713                    | "field_designator"
15714                    | "enumerator"
15715            )
15716        {
15717            return true;
15718        }
15719        if matches!(
15720            parent.kind(),
15721            "translation_unit"
15722                | "function_definition"
15723                | "compound_statement"
15724                | "declaration"
15725                | "field_declaration"
15726                | "parameter_declaration"
15727        ) {
15728            return false;
15729        }
15730        node = parent;
15731    }
15732    false
15733}
15734
15735/// Whether a parameter declaration belongs to the callable scope whose body can
15736/// contain references to it.
15737///
15738/// Error recovery can wrap a macro-decorated class body in a synthetic outer
15739/// `function_definition`. Merely finding any callable ancestor would then leak
15740/// parameters from member prototypes into later member bodies. Require the
15741/// parameter to be inside that definition's own declarator instead.
15742pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
15743    let mut current = parameter.parent();
15744    while let Some(ancestor) = current {
15745        if ancestor.kind() == "lambda_expression" {
15746            return ancestor
15747                .child_by_field_name("declarator")
15748                .is_some_and(|declarator| {
15749                    declarator.start_byte() <= parameter.start_byte()
15750                        && parameter.end_byte() <= declarator.end_byte()
15751                });
15752        }
15753        if ancestor.kind() == "function_definition" {
15754            return ancestor
15755                .child_by_field_name("declarator")
15756                .is_some_and(|declarator| {
15757                    declarator.start_byte() <= parameter.start_byte()
15758                        && parameter.end_byte() <= declarator.end_byte()
15759                });
15760        }
15761        current = ancestor.parent();
15762    }
15763    false
15764}
15765
15766pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
15767    let mut current = node.parent();
15768    while let Some(ancestor) = current {
15769        if matches!(
15770            ancestor.kind(),
15771            "parameter_declaration" | "optional_parameter_declaration"
15772        ) {
15773            return ancestor
15774                .child_by_field_name("type")
15775                .is_some_and(|type_node| {
15776                    type_node.start_byte() <= node.start_byte()
15777                        && node.end_byte() <= type_node.end_byte()
15778                });
15779        }
15780        if matches!(
15781            ancestor.kind(),
15782            "function_definition" | "lambda_expression" | "compound_statement"
15783        ) {
15784            return false;
15785        }
15786        current = ancestor.parent();
15787    }
15788    false
15789}
15790
15791fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
15792    if specifier.child_by_field_name("body").is_some() {
15793        return true;
15794    }
15795    let mut current = specifier.parent();
15796    while let Some(ancestor) = current {
15797        match ancestor.kind() {
15798            "type_descriptor"
15799            | "parameter_declaration"
15800            | "optional_parameter_declaration"
15801            | "template_argument_list"
15802            | "cast_expression" => return false,
15803            "declaration" | "field_declaration" => {
15804                let mut cursor = ancestor.walk();
15805                return ancestor
15806                    .children_by_field_name("declarator", &mut cursor)
15807                    .next()
15808                    .is_none();
15809            }
15810            "translation_unit" => return true,
15811            _ => current = ancestor.parent(),
15812        }
15813    }
15814    false
15815}
15816
15817pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
15818    match node.kind() {
15819        "identifier"
15820        | "field_identifier"
15821        | "qualified_identifier"
15822        | "scoped_identifier"
15823        | "operator_name"
15824        | "destructor_name"
15825        | "literal_operator_name" => Some(node),
15826        "reference_declarator" | "parenthesized_declarator" => {
15827            node.named_child(0).and_then(declarator_name_node)
15828        }
15829        _ => node
15830            .child_by_field_name("declarator")
15831            .or_else(|| node.child_by_field_name("name"))
15832            .or_else(|| node.child_by_field_name("field"))
15833            .and_then(declarator_name_node),
15834    }
15835}
15836
15837fn declarator_name_path_contains(
15838    declarator: Node<'_>,
15839    candidate: Node<'_>,
15840    allow_type_identifier: bool,
15841) -> bool {
15842    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
15843        return false;
15844    };
15845    let mut current = Some(declarator);
15846    while let Some(node) = current {
15847        if same_node(node, candidate) {
15848            return true;
15849        }
15850        if same_node(node, name) {
15851            return false;
15852        }
15853        current = node
15854            .child_by_field_name("declarator")
15855            .or_else(|| node.child_by_field_name("name"))
15856            .or_else(|| node.child_by_field_name("field"));
15857    }
15858    false
15859}
15860
15861fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
15862    match node.kind() {
15863        "identifier"
15864        | "field_identifier"
15865        | "operator_name"
15866        | "destructor_name"
15867        | "literal_operator_name" => Some(node),
15868        "type_identifier" if allow_type_identifier => Some(node),
15869        _ => node
15870            .child_by_field_name("declarator")
15871            .or_else(|| node.child_by_field_name("name"))
15872            .or_else(|| node.child_by_field_name("field"))
15873            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
15874    }
15875}
15876
15877/// True when `node` is a component of a larger structured type node whose outer
15878/// range is the single reference surfaced to callers.
15879pub fn is_nested_type_node(node: Node<'_>) -> bool {
15880    node.parent().is_some_and(|parent| {
15881        matches!(
15882            parent.kind(),
15883            "qualified_identifier" | "scoped_type_identifier" | "template_type"
15884        )
15885    })
15886}
15887
15888pub struct OutOfLineMemberDefinitionOwners<'tree> {
15889    pub owners: Vec<(Node<'tree>, CodeUnit)>,
15890    innermost: Option<(Node<'tree>, CodeUnit)>,
15891}
15892
15893impl OutOfLineMemberDefinitionOwners<'_> {
15894    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
15895        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
15896    }
15897}
15898
15899pub struct QualifiedOwnerComponents<'tree> {
15900    pub nodes: Vec<Node<'tree>>,
15901    pub names: Vec<String>,
15902    pub global: bool,
15903}
15904
15905/// True when each structured qualifier on the callable-name path has a real
15906/// `::` token. A macro-prefixed return type can make tree-sitter insert a
15907/// zero-width missing separator and parse `TYPE Result<T> method()` as the
15908/// false qualified declarator `Result<T>::method`.
15909pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
15910    let mut stack = vec![node];
15911    let mut found_separator = false;
15912    while let Some(current) = stack.pop() {
15913        if !matches!(
15914            current.kind(),
15915            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15916        ) {
15917            continue;
15918        }
15919        let mut current_has_separator = false;
15920        for child in children_iter(current) {
15921            if child.kind() == "::" {
15922                if child.is_missing() {
15923                    return false;
15924                }
15925                current_has_separator = true;
15926                found_separator = true;
15927            }
15928        }
15929        if !current_has_separator {
15930            return false;
15931        }
15932        for field in ["scope", "name"] {
15933            if let Some(child) = current.child_by_field_name(field)
15934                && matches!(
15935                    child.kind(),
15936                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15937                )
15938            {
15939                stack.push(child);
15940            }
15941        }
15942    }
15943    found_separator
15944}
15945
15946pub fn qualified_owner_components<'tree>(
15947    node: Node<'tree>,
15948    source: &str,
15949) -> Option<QualifiedOwnerComponents<'tree>> {
15950    if !qualified_name_has_concrete_scope_separators(node) {
15951        return None;
15952    }
15953    let mut nodes = cpp_name_component_nodes(node)?;
15954    nodes.pop()?;
15955    if nodes.is_empty() {
15956        return None;
15957    }
15958    let names = nodes
15959        .iter()
15960        .map(|component| node_text(*component, source).to_string())
15961        .collect();
15962    Some(QualifiedOwnerComponents {
15963        nodes,
15964        names,
15965        global: is_globally_qualified_cpp_name(node),
15966    })
15967}
15968
15969pub fn out_of_line_member_definition_owner<'tree>(
15970    analyzer: &CppGraphSource<'_>,
15971    visibility: &VisibilityIndex<'_>,
15972    file: &ProjectFile,
15973    source: &str,
15974    node: Node<'tree>,
15975) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
15976    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
15977        || !has_ancestor_kind(node, "function_definition")
15978        || !is_function_declarator_name_root(node)
15979    {
15980        return None;
15981    }
15982    let qualified = qualified_owner_components(node, source)?;
15983    let lexical_scope = enclosing_namespace_components(node, source)?;
15984    let mut owners = Vec::new();
15985    let mut innermost = None;
15986
15987    for component_count in 1..=qualified.names.len() {
15988        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
15989            .resolve_type_components_lexically(
15990                analyzer,
15991                file,
15992                &qualified.names[..component_count],
15993                qualified.global,
15994                &lexical_scope,
15995            )
15996            && !owners
15997                .iter()
15998                .any(|(_, existing)| same_visible_symbol(existing, &unit))
15999        {
16000            if component_count == qualified.names.len() {
16001                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
16002            }
16003            owners.push((qualified.nodes[component_count - 1], unit));
16004        }
16005    }
16006
16007    // The C++ analyzer has already reconciled an indexed out-of-line callable
16008    // against the include-visible class table. Consult that canonical owner
16009    // chain only when ordinary lexical lookup could not recover the innermost
16010    // owner.  A one-segment qualifier is safe here only when the enclosing
16011    // indexed callable has an authoritative class owner and the parser's
16012    // namespace path is a (possibly sparse) subsequence of that owner path.
16013    // The latter is what lets macro-wrapped namespace sentinels recover a
16014    // missing `time_internal`/`cord_internal` component without guessing an
16015    // unrelated short name.
16016    if innermost.is_none() {
16017        let indexed_owner_components = visibility
16018            .indexed_enclosing_owner_scope(analyzer, file, node)
16019            .or_else(|| {
16020                // Retain the legacy rendered-name fallback for the existing
16021                // multi-segment path when an enclosing owner chain is not
16022                // available (for example, cache-loaded units without parent
16023                // links).  One-segment recovery must stay canonical-only.
16024                if qualified.names.len() <= 1 {
16025                    return None;
16026                }
16027                let range = Range {
16028                    start_byte: node.start_byte(),
16029                    end_byte: node.end_byte(),
16030                    start_line: node.start_position().row,
16031                    end_line: node.end_position().row,
16032                };
16033                let start = analyzer.enclosing_code_unit(file, &range)?;
16034                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16035                    brokk_bifrost_core::analyzer::Language::Cpp,
16036                    &cpp_name_for(&start),
16037                );
16038                components.pop();
16039                Some(components)
16040            });
16041        if let Some(indexed_owner_components) = indexed_owner_components
16042            && indexed_owner_components.len() > qualified.names.len()
16043            && indexed_owner_components.ends_with(&qualified.names)
16044            && indexed_namespace_path_is_recoverable(
16045                &lexical_scope,
16046                &indexed_owner_components,
16047                qualified.names.len(),
16048            )
16049            // A globally-qualified one-segment owner is an explicit request
16050            // for the top-level binding; do not reinterpret it as a missing
16051            // namespace component.  Existing multi-segment global lookups
16052            // retain their historical indexed recovery.
16053            && (qualified.names.len() > 1 || !qualified.global)
16054        {
16055            let namespace_count = indexed_owner_components.len() - qualified.names.len();
16056            for component_count in 1..=qualified.names.len() {
16057                let expected = &indexed_owner_components[..namespace_count + component_count];
16058                let owner_node = qualified.nodes[component_count - 1];
16059                for owner in visibility
16060                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
16061                    .filter(|candidate| candidate.is_class())
16062                    .filter(|candidate| {
16063                        canonical_cpp_scope_components(candidate) == expected
16064                            && visibility.external_type_candidate_visible_in_context(
16065                                analyzer, file, candidate, node,
16066                            )
16067                    })
16068                {
16069                    if component_count == qualified.names.len() && innermost.is_none() {
16070                        innermost = Some((owner_node, owner.clone()));
16071                    }
16072                    if !owners
16073                        .iter()
16074                        .any(|(_, existing)| same_symbol(existing, owner))
16075                    {
16076                        owners.push((owner_node, owner.clone()));
16077                    }
16078                }
16079            }
16080        }
16081    }
16082    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
16083}
16084
16085fn is_function_declarator_name_root(node: Node<'_>) -> bool {
16086    let mut current = node;
16087    while let Some(parent) = current.parent() {
16088        if parent.kind() == "function_declarator" {
16089            return parent.child_by_field_name("declarator") == Some(current);
16090        }
16091        if matches!(
16092            parent.kind(),
16093            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
16094        ) && parent.child_by_field_name("declarator") == Some(current)
16095        {
16096            current = parent;
16097            continue;
16098        }
16099        return false;
16100    }
16101    false
16102}
16103
16104pub fn append_cpp_name_components(
16105    node: Node<'_>,
16106    source: &str,
16107    out: &mut Vec<String>,
16108) -> Option<()> {
16109    out.extend(
16110        cpp_name_component_nodes(node)?
16111            .into_iter()
16112            .map(|component| node_text(component, source).to_string()),
16113    );
16114    Some(())
16115}
16116
16117pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16118    let mut components = Vec::new();
16119    append_cpp_name_components(node, source, &mut components)?;
16120    Some(components)
16121}
16122
16123/// Resolve a structured type spelling from an object-like macro replacement
16124/// when definition-site source order has no answer.
16125///
16126/// Macro replacement tokens are looked up where the macro is expanded, so a
16127/// type declared later in the defining header can still be their destination.
16128/// Without expanding every invocation, accept only one include-visible logical
16129/// class or alias whose structured path ends in the replacement components.
16130/// An ordinary lexical answer always takes precedence at the call site.
16131pub fn unique_macro_replacement_type_candidate(
16132    analyzer: &CppGraphSource<'_>,
16133    visibility: &VisibilityIndex<'_>,
16134    file: &ProjectFile,
16135    components: &[String],
16136) -> Option<CodeUnit> {
16137    let terminal = components.last()?;
16138    let mut candidates = Vec::new();
16139    for candidate in visibility
16140        .visible_identifier_candidates(file, terminal)
16141        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
16142        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
16143    {
16144        if !candidates
16145            .iter()
16146            .any(|existing| same_logical_symbol(existing, candidate))
16147        {
16148            candidates.push(candidate.clone());
16149        }
16150    }
16151    (candidates.len() == 1).then(|| candidates.remove(0))
16152}
16153
16154/// The base scopes named by member using-declarations for `member` in one
16155/// class source range.
16156///
16157/// The grammar supplies the qualified identifier and each component. Keep
16158/// this interpretation shared between forward overload lookup and inverse
16159/// owner routing rather than reparsing a rendered `Base::member` string at
16160/// either call site.
16161pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
16162    let mut parser = Parser::new();
16163    if parser
16164        .set_language(&tree_sitter_cpp::LANGUAGE.into())
16165        .is_err()
16166    {
16167        return Vec::new();
16168    }
16169    let Some(tree) = parser.parse(source, None) else {
16170        return Vec::new();
16171    };
16172    let mut scopes = Vec::new();
16173    let mut pending = vec![tree.root_node()];
16174    while let Some(node) = pending.pop() {
16175        if node.kind() == "using_declaration" {
16176            let Some(imported) = node.named_child(0) else {
16177                continue;
16178            };
16179            let Some(mut components) = cpp_type_name_components(imported, source) else {
16180                continue;
16181            };
16182            if components.pop().as_deref() == Some(member) && !components.is_empty() {
16183                scopes.push(components.join("::"));
16184            }
16185            continue;
16186        }
16187        push_named_children_reversed(node, &mut pending);
16188    }
16189    scopes
16190}
16191
16192/// Whether a structured using-declaration scope can name `qualified` as an
16193/// ancestor class. The boundary check prevents `Base` from matching
16194/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
16195pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
16196    qualified == scope
16197        || qualified
16198            .strip_suffix(scope)
16199            .is_some_and(|prefix| prefix.ends_with("::"))
16200}
16201
16202/// Whether `node` is the direct structured type payload of a template
16203/// argument. This role remains meaningful even when a surrounding expression
16204/// is below tree-sitter recovery, because both the `template_argument_list`
16205/// and the `type_descriptor` retain their named fields.
16206pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
16207    let Some(type_descriptor) = node.parent() else {
16208        return false;
16209    };
16210    if type_descriptor.kind() != "type_descriptor"
16211        || type_descriptor.child_by_field_name("type") != Some(node)
16212    {
16213        return false;
16214    }
16215    let Some(arguments) = type_descriptor.parent() else {
16216        return false;
16217    };
16218    if arguments.kind() != "template_argument_list" {
16219        return false;
16220    }
16221    arguments.parent().is_some_and(|parent| {
16222        matches!(parent.kind(), "template_type" | "template_function")
16223            && parent.child_by_field_name("arguments") == Some(arguments)
16224    })
16225}
16226
16227pub fn cpp_template_reference_arguments(
16228    mut node: Node<'_>,
16229    source: &str,
16230) -> Option<Vec<CppTemplateExpression>> {
16231    loop {
16232        match node.kind() {
16233            "template_type" | "template_function" => {
16234                let arguments = node.child_by_field_name("arguments")?;
16235                let mut cursor = arguments.walk();
16236                return Some(
16237                    arguments
16238                        .named_children(&mut cursor)
16239                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
16240                        .map(|argument| CppTemplateExpression {
16241                            text: normalize_cpp_whitespace(node_text(argument, source)),
16242                            // One template term from a resolver query; see `ParentIndex::unindexed`.
16243                            term: cpp_template_term(
16244                                argument,
16245                                source,
16246                                &[],
16247                                &ParentIndex::unindexed(),
16248                            ),
16249                        })
16250                        .collect(),
16251                );
16252            }
16253            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
16254                node = node
16255                    .child_by_field_name("name")
16256                    .or_else(|| node.child_by_field_name("type"))?;
16257            }
16258            _ => return None,
16259        }
16260    }
16261}
16262
16263fn cpp_reconcile_primary_template_parameters(
16264    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
16265    preferred: &CodeUnit,
16266) -> Option<Vec<CppTemplateParameterMetadata>> {
16267    let canonical = candidates
16268        .iter()
16269        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
16270    let mut merged = canonical
16271        .parameters
16272        .iter()
16273        .map(|parameter| CppTemplateParameterMetadata {
16274            name: parameter.name.clone(),
16275            kind: parameter.kind,
16276            variadic: parameter.variadic,
16277            default: None,
16278        })
16279        .collect::<Vec<_>>();
16280
16281    for (_, metadata) in candidates {
16282        if metadata.parameters.len() != merged.len() {
16283            return None;
16284        }
16285        let rename_bindings = metadata
16286            .parameters
16287            .iter()
16288            .zip(&merged)
16289            .map(|(parameter, canonical)| {
16290                (
16291                    parameter.name.clone(),
16292                    CppTemplateTerm::Parameter(canonical.name.clone()),
16293                )
16294            })
16295            .collect::<HashMap<_, _>>();
16296        for ((parameter, canonical), merged_parameter) in metadata
16297            .parameters
16298            .iter()
16299            .zip(&canonical.parameters)
16300            .zip(&mut merged)
16301        {
16302            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
16303                return None;
16304            }
16305            let Some(default) = &parameter.default else {
16306                continue;
16307            };
16308            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
16309            if let Some(existing) = &merged_parameter.default {
16310                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
16311                    return None;
16312                }
16313            } else {
16314                merged_parameter.default = Some(CppTemplateExpression {
16315                    text: default.text.clone(),
16316                    term: normalized_term,
16317                });
16318            }
16319        }
16320    }
16321    Some(merged)
16322}
16323
16324pub fn cpp_bind_template_arguments(
16325    parameters: &[CppTemplateParameterMetadata],
16326    explicit_arguments: &[CppTemplateExpression],
16327) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
16328    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
16329    if variadic_index.is_some_and(|index| {
16330        index + 1 != parameters.len()
16331            || parameters[index + 1..]
16332                .iter()
16333                .any(|parameter| parameter.variadic)
16334    }) {
16335        return None;
16336    }
16337    let fixed_count = variadic_index.unwrap_or(parameters.len());
16338    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
16339        return None;
16340    }
16341    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
16342    let mut expanded = explicit_arguments[..explicit_fixed_count]
16343        .iter()
16344        .map(cpp_clone_template_expression_iterative)
16345        .collect::<Vec<_>>();
16346    let mut bindings = HashMap::default();
16347    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
16348        bindings.insert(
16349            parameter.name.clone(),
16350            cpp_clone_template_term_iterative(&argument.term),
16351        );
16352    }
16353    for parameter in &parameters[explicit_fixed_count..fixed_count] {
16354        let default = parameter.default.as_ref()?;
16355        let term = cpp_substitute_template_term(&default.term, &bindings)?;
16356        bindings.insert(parameter.name.clone(), term.clone());
16357        expanded.push(CppTemplateExpression {
16358            text: default.text.clone(),
16359            term,
16360        });
16361    }
16362    if let Some(index) = variadic_index {
16363        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
16364        expanded.extend(
16365            packed_arguments
16366                .iter()
16367                .map(cpp_clone_template_expression_iterative),
16368        );
16369        bindings.insert(
16370            parameters[index].name.clone(),
16371            CppTemplateTerm::Node {
16372                kind: "parameter_pack".to_string(),
16373                children: packed_arguments
16374                    .iter()
16375                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
16376                    .collect(),
16377            },
16378        );
16379    }
16380    Some((expanded, bindings))
16381}
16382
16383fn cpp_specialization_matches(
16384    metadata: &CppTemplateMetadata,
16385    arguments: &[CppTemplateExpression],
16386) -> bool {
16387    if metadata.specialization_arguments.len() != arguments.len() {
16388        return false;
16389    }
16390    let parameter_names = metadata
16391        .parameters
16392        .iter()
16393        .map(|parameter| parameter.name.as_str())
16394        .collect::<HashSet<_>>();
16395    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16396    for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
16397        if !cpp_unify_template_term(
16398            &pattern.term,
16399            &argument.term,
16400            &parameter_names,
16401            &mut bindings,
16402        ) {
16403            return false;
16404        }
16405    }
16406    true
16407}
16408
16409fn cpp_specialization_more_specialized(
16410    candidate: &CppTemplateMetadata,
16411    other: &CppTemplateMetadata,
16412) -> bool {
16413    cpp_specialization_pattern_accepts(other, candidate)
16414        && !cpp_specialization_pattern_accepts(candidate, other)
16415}
16416
16417fn cpp_specialization_pattern_accepts(
16418    broader: &CppTemplateMetadata,
16419    narrower: &CppTemplateMetadata,
16420) -> bool {
16421    if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
16422        return false;
16423    }
16424    let parameter_names = broader
16425        .parameters
16426        .iter()
16427        .map(|parameter| parameter.name.as_str())
16428        .collect::<HashSet<_>>();
16429    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16430    broader
16431        .specialization_arguments
16432        .iter()
16433        .zip(&narrower.specialization_arguments)
16434        .all(|(pattern, argument)| {
16435            cpp_unify_template_term(
16436                &pattern.term,
16437                &argument.term,
16438                &parameter_names,
16439                &mut bindings,
16440            )
16441        })
16442}
16443
16444pub fn cpp_substitute_template_term(
16445    term: &CppTemplateTerm,
16446    bindings: &HashMap<String, CppTemplateTerm>,
16447) -> Option<CppTemplateTerm> {
16448    enum Work<'a> {
16449        Visit(&'a CppTemplateTerm),
16450        Build { kind: String, child_count: usize },
16451    }
16452
16453    let mut work = vec![Work::Visit(term)];
16454    let mut substituted = Vec::new();
16455    while let Some(next) = work.pop() {
16456        match next {
16457            Work::Visit(CppTemplateTerm::Parameter(name)) => {
16458                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
16459            }
16460            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16461                substituted.push(CppTemplateTerm::Atom {
16462                    kind: kind.clone(),
16463                    text: text.clone(),
16464                });
16465            }
16466            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16467                work.push(Work::Build {
16468                    kind: kind.clone(),
16469                    child_count: children.len(),
16470                });
16471                work.extend(children.iter().rev().map(Work::Visit));
16472            }
16473            Work::Build { kind, child_count } => {
16474                let children = substituted.split_off(substituted.len() - child_count);
16475                substituted.push(CppTemplateTerm::Node { kind, children });
16476            }
16477        }
16478    }
16479    substituted.pop()
16480}
16481
16482pub fn cpp_substitute_template_arguments(
16483    arguments: &[CppTemplateExpression],
16484    bindings: &HashMap<String, CppTemplateTerm>,
16485) -> Option<Vec<CppTemplateExpression>> {
16486    let mut substituted = Vec::new();
16487    for argument in arguments {
16488        let CppTemplateTerm::Node { kind, children } = &argument.term else {
16489            substituted.push(CppTemplateExpression {
16490                text: argument.text.clone(),
16491                term: cpp_substitute_template_term(&argument.term, bindings)?,
16492            });
16493            continue;
16494        };
16495        if kind != "parameter_pack_expansion" {
16496            substituted.push(CppTemplateExpression {
16497                text: argument.text.clone(),
16498                term: cpp_substitute_template_term(&argument.term, bindings)?,
16499            });
16500            continue;
16501        }
16502        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
16503            return None;
16504        };
16505        if ellipsis != "..." {
16506            return None;
16507        }
16508
16509        let mut pack_names = Vec::new();
16510        let mut work = vec![pattern];
16511        while let Some(term) = work.pop() {
16512            match term {
16513                CppTemplateTerm::Parameter(name)
16514                    if matches!(
16515                        bindings.get(name),
16516                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
16517                    ) =>
16518                {
16519                    if !pack_names.contains(name) {
16520                        pack_names.push(name.clone());
16521                    }
16522                }
16523                CppTemplateTerm::Node { children, .. } => work.extend(children),
16524                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16525            }
16526        }
16527        let first_pack = pack_names.first()?;
16528        let CppTemplateTerm::Node {
16529            children: first_elements,
16530            ..
16531        } = bindings.get(first_pack)?
16532        else {
16533            return None;
16534        };
16535        let pack_len = first_elements.len();
16536        for pack_name in &pack_names {
16537            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16538                return None;
16539            };
16540            if children.len() != pack_len {
16541                return None;
16542            }
16543        }
16544        for index in 0..pack_len {
16545            let mut element_bindings = bindings.clone();
16546            for pack_name in &pack_names {
16547                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16548                    return None;
16549                };
16550                element_bindings.insert(
16551                    pack_name.clone(),
16552                    cpp_clone_template_term_iterative(&children[index]),
16553                );
16554            }
16555            substituted.push(CppTemplateExpression {
16556                text: argument.text.clone(),
16557                term: cpp_substitute_template_term(pattern, &element_bindings)?,
16558            });
16559        }
16560    }
16561    Some(substituted)
16562}
16563
16564fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
16565    enum Work<'a> {
16566        Visit(&'a CppTemplateTerm),
16567        Build { kind: String, child_count: usize },
16568    }
16569
16570    let mut work = vec![Work::Visit(term)];
16571    let mut cloned = Vec::new();
16572    while let Some(next) = work.pop() {
16573        match next {
16574            Work::Visit(CppTemplateTerm::Parameter(name)) => {
16575                cloned.push(CppTemplateTerm::Parameter(name.clone()));
16576            }
16577            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16578                cloned.push(CppTemplateTerm::Atom {
16579                    kind: kind.clone(),
16580                    text: text.clone(),
16581                });
16582            }
16583            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16584                work.push(Work::Build {
16585                    kind: kind.clone(),
16586                    child_count: children.len(),
16587                });
16588                work.extend(children.iter().rev().map(Work::Visit));
16589            }
16590            Work::Build { kind, child_count } => {
16591                let children = cloned.split_off(cloned.len() - child_count);
16592                cloned.push(CppTemplateTerm::Node { kind, children });
16593            }
16594        }
16595    }
16596    cloned
16597        .pop()
16598        .expect("template term traversal emits one root")
16599}
16600
16601fn cpp_clone_template_expression_iterative(
16602    expression: &CppTemplateExpression,
16603) -> CppTemplateExpression {
16604    CppTemplateExpression {
16605        text: expression.text.clone(),
16606        term: cpp_clone_template_term_iterative(&expression.term),
16607    }
16608}
16609
16610pub fn cpp_unify_template_term(
16611    pattern: &CppTemplateTerm,
16612    argument: &CppTemplateTerm,
16613    parameters: &HashSet<&str>,
16614    bindings: &mut HashMap<String, CppTemplateTerm>,
16615) -> bool {
16616    let mut work = vec![(pattern, argument)];
16617    while let Some((pattern, argument)) = work.pop() {
16618        match pattern {
16619            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
16620                if let Some(bound) = bindings.get(name) {
16621                    if !cpp_template_terms_equal(bound, argument) {
16622                        return false;
16623                    }
16624                } else {
16625                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
16626                }
16627            }
16628            CppTemplateTerm::Atom {
16629                kind: pattern_kind,
16630                text: pattern_text,
16631            } => {
16632                if !matches!(
16633                    argument,
16634                    CppTemplateTerm::Atom { kind, text }
16635                        if kind == pattern_kind && text == pattern_text
16636                ) {
16637                    return false;
16638                }
16639            }
16640            CppTemplateTerm::Node {
16641                kind: pattern_kind,
16642                children: pattern_children,
16643            } => {
16644                let CppTemplateTerm::Node { kind, children } = argument else {
16645                    return false;
16646                };
16647                if kind != pattern_kind || children.len() != pattern_children.len() {
16648                    return false;
16649                }
16650                work.extend(pattern_children.iter().zip(children).rev());
16651            }
16652            CppTemplateTerm::Parameter(_) => return false,
16653        }
16654    }
16655    true
16656}
16657
16658fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
16659    let mut work = vec![(left, right)];
16660    while let Some((left, right)) = work.pop() {
16661        match (left, right) {
16662            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
16663                if left != right {
16664                    return false;
16665                }
16666            }
16667            (
16668                CppTemplateTerm::Atom {
16669                    kind: left_kind,
16670                    text: left_text,
16671                },
16672                CppTemplateTerm::Atom {
16673                    kind: right_kind,
16674                    text: right_text,
16675                },
16676            ) => {
16677                if left_kind != right_kind || left_text != right_text {
16678                    return false;
16679                }
16680            }
16681            (
16682                CppTemplateTerm::Node {
16683                    kind: left_kind,
16684                    children: left_children,
16685                },
16686                CppTemplateTerm::Node {
16687                    kind: right_kind,
16688                    children: right_children,
16689                },
16690            ) => {
16691                if left_kind != right_kind || left_children.len() != right_children.len() {
16692                    return false;
16693                }
16694                work.extend(left_children.iter().zip(right_children).rev());
16695            }
16696            _ => return false,
16697        }
16698    }
16699    true
16700}
16701
16702pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
16703    let mut components = Vec::new();
16704    let mut stack = vec![node];
16705    while let Some(current) = stack.pop() {
16706        match current.kind() {
16707            "identifier"
16708            | "field_identifier"
16709            | "namespace_identifier"
16710            | "type_identifier"
16711            | "operator_name"
16712            | "destructor_name" => components.push(current),
16713            "template_type" | "template_function" => {
16714                stack.push(current.child_by_field_name("name")?);
16715            }
16716            "dependent_name" => stack.push(current.named_child(0)?),
16717            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
16718                stack.push(current.child_by_field_name("name")?);
16719                if let Some(scope) = current.child_by_field_name("scope") {
16720                    stack.push(scope);
16721                }
16722            }
16723            "nested_namespace_specifier" => {
16724                for index in (0..current.named_child_count()).rev() {
16725                    stack.push(current.named_child(index)?);
16726                }
16727            }
16728            _ => return None,
16729        }
16730    }
16731    Some(components)
16732}
16733
16734pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
16735    node.child_by_field_name("scope").is_none()
16736        && node.child(0).is_some_and(|child| child.kind() == "::")
16737}
16738
16739fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16740    let mut namespaces = Vec::new();
16741    let mut current = node.parent();
16742    while let Some(parent) = current {
16743        if parent.kind() == "namespace_definition"
16744            && let Some(name) = parent.child_by_field_name("name")
16745        {
16746            let mut components = Vec::new();
16747            append_cpp_name_components(name, source, &mut components)?;
16748            namespaces.push(components);
16749        }
16750        current = parent.parent();
16751    }
16752    namespaces.reverse();
16753    Some(namespaces.into_iter().flatten().collect())
16754}
16755
16756/// Whether a parser-derived namespace path can be reconciled with an indexed
16757/// owner scope without inventing an unrelated short-name binding.
16758///
16759/// Macro namespace sentinels can make tree-sitter omit one or more namespace
16760/// definitions from the ancestor chain. Preserve the order of every namespace
16761/// that did survive parsing, but allow indexed components between them. An
16762/// empty path is accepted only when the declarator itself supplies a nested
16763/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
16764/// owner chain, that suffix is structural evidence that a namespace was lost.
16765/// A one-segment owner at the translation-unit root remains insufficient.
16766fn indexed_namespace_path_is_recoverable(
16767    lexical_scope: &[String],
16768    indexed_owner_scope: &[String],
16769    explicit_owner_component_count: usize,
16770) -> bool {
16771    if lexical_scope.is_empty() {
16772        return explicit_owner_component_count > 1;
16773    }
16774    if lexical_scope.len() >= indexed_owner_scope.len() {
16775        return false;
16776    }
16777    let mut indexed = indexed_owner_scope.iter();
16778    lexical_scope
16779        .iter()
16780        .all(|component| indexed.any(|candidate| candidate == component))
16781}
16782
16783pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
16784    let mut current = node.parent();
16785    while let Some(parent) = current {
16786        if parent.kind() == kind {
16787            return true;
16788        }
16789        current = parent.parent();
16790    }
16791    false
16792}
16793
16794/// Whether a declaration type is initialized with a pointer cast.
16795///
16796/// This structured shape has an independent qualified occurrence in addition
16797/// to the cast descriptor below it. Other declarations must keep their normal
16798/// full-range occurrence only.
16799pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
16800    let mut current = Some(node);
16801    while let Some(candidate) = current {
16802        if candidate.kind() == "declaration" {
16803            let Some(type_node) = candidate.child_by_field_name("type") else {
16804                return false;
16805            };
16806            if !(type_node.start_byte() <= node.start_byte()
16807                && node.end_byte() <= type_node.end_byte())
16808            {
16809                return false;
16810            }
16811            let mut cursor = candidate.walk();
16812            return candidate.named_children(&mut cursor).any(|child| {
16813                child.kind() == "init_declarator"
16814                    && child
16815                        .child_by_field_name("value")
16816                        .is_some_and(|value| value.kind() == "cast_expression")
16817            });
16818        }
16819        current = candidate.parent();
16820    }
16821    false
16822}
16823
16824#[derive(Clone, Copy, PartialEq, Eq)]
16825pub(crate) enum QualifiedAliasReferenceKind {
16826    Ordinary,
16827    ConstructorWithExpressionArgument,
16828    ExhaustiveTemplate,
16829}
16830
16831/// Whether a qualified alias reference preserves the requested target.
16832///
16833/// The complete qualified spelling and its terminal identifier are both valid
16834/// occurrences when the visible alias path is structurally proven to name the
16835/// target. Template aliases use their bound arguments; ordinary aliases use
16836/// their structured primary chain.
16837pub(crate) fn qualified_alias_reference_preserves_target(
16838    node: Node<'_>,
16839    target: &CodeUnit,
16840    analyzer: &CppGraphSource<'_>,
16841    visibility: &VisibilityIndex<'_>,
16842    file: &ProjectFile,
16843    source: &str,
16844) -> Option<QualifiedAliasReferenceKind> {
16845    if !matches!(
16846        node.kind(),
16847        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16848    ) {
16849        return None;
16850    }
16851    let components = cpp_type_name_components(node, source)?;
16852    let name = components.last()?;
16853    analyzer.type_alias_provider().and_then(|provider| {
16854        visibility
16855            .visible_identifier_candidates(file, name)
16856            .find_map(|candidate| {
16857                let proof = provider.is_type_alias(candidate)
16858                    && canonical_cpp_scope_components(candidate) == components
16859                    && visibility.external_type_candidate_visible_in_context(
16860                        analyzer, file, candidate, node,
16861                    )
16862                    && match cpp_template_reference_arguments(node, source) {
16863                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
16864                            analyzer, file, candidate, &arguments, target,
16865                        ),
16866                        None => visibility.structured_alias_primary_preserves_target(
16867                            analyzer, file, candidate, target,
16868                        ),
16869                    };
16870                proof.then(|| {
16871                    if cpp_template_reference_arguments(node, source).is_some()
16872                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
16873                            analyzer, file, candidate,
16874                        )
16875                    {
16876                        QualifiedAliasReferenceKind::ExhaustiveTemplate
16877                    } else if qualified_alias_constructor_has_expression_argument(node)
16878                        || qualified_alias_local_constructor_declaration(node)
16879                    {
16880                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16881                    } else {
16882                        QualifiedAliasReferenceKind::Ordinary
16883                    }
16884                })
16885            })
16886    })
16887}
16888
16889pub(crate) fn qualified_alias_reference_requires_terminal(
16890    reference: Option<QualifiedAliasReferenceKind>,
16891) -> bool {
16892    matches!(
16893        reference,
16894        Some(
16895            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16896                | QualifiedAliasReferenceKind::ExhaustiveTemplate
16897        )
16898    )
16899}
16900
16901fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
16902    let Some(declaration) = node.parent().filter(|parent| {
16903        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16904    }) else {
16905        return false;
16906    };
16907    let mut cursor = declaration.walk();
16908    declaration.named_children(&mut cursor).any(|child| {
16909        child.kind() == "init_declarator"
16910            && child
16911                .child_by_field_name("value")
16912                .filter(|value| value.kind() == "argument_list")
16913                .is_some_and(|arguments| {
16914                    let mut cursor = arguments.walk();
16915                    arguments.named_children(&mut cursor).any(|argument| {
16916                        let is_parameter = matches!(
16917                            argument.kind(),
16918                            "parameter_declaration" | "optional_parameter_declaration"
16919                        );
16920                        if is_parameter {
16921                            argument
16922                                .child_by_field_name("type")
16923                                .is_some_and(|type_node| {
16924                                    type_node.kind() == "type_identifier"
16925                                        && argument.child_by_field_name("declarator").is_none()
16926                                })
16927                        } else {
16928                            !argument.kind().ends_with("_literal")
16929                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
16930                        }
16931                    })
16932                })
16933    })
16934}
16935
16936/// Tree-sitter represents a local C++ direct construction such as
16937/// `Alias value(argument)` as a function declarator. Restrict that recovery to
16938/// declarations inside a compound statement so namespace-scope function
16939/// declarations with the same qualified return type stay full-range only.
16940fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
16941    let Some(declaration) = node.parent().filter(|parent| {
16942        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16943    }) else {
16944        return false;
16945    };
16946    if declaration
16947        .parent()
16948        .is_none_or(|parent| parent.kind() != "compound_statement")
16949    {
16950        return false;
16951    }
16952    let mut cursor = declaration.walk();
16953    declaration
16954        .named_children(&mut cursor)
16955        .any(|child| child.kind() == "function_declarator")
16956}
16957
16958/// Return the terminal identifier represented by a callable or type callee.
16959///
16960/// Qualified, scoped, template, and field wrappers are traversed through their
16961/// grammar fields so both function calls and type constructions emit the token
16962/// that names the referenced declaration.
16963pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
16964    loop {
16965        let next = match node.kind() {
16966            "qualified_identifier"
16967            | "scoped_identifier"
16968            | "template_method"
16969            | "template_function"
16970            | "template_type" => node.child_by_field_name("name"),
16971            "field_expression" => node.child_by_field_name("field"),
16972            _ => None,
16973        };
16974        let Some(next) = next else {
16975            return node;
16976        };
16977        node = next;
16978    }
16979}
16980
16981#[derive(Clone, Copy)]
16982pub struct RecoveredRelationalTemplateMemberCall<'tree> {
16983    pub receiver: Node<'tree>,
16984    pub member: Node<'tree>,
16985    pub arity: usize,
16986}
16987
16988/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
16989/// nested relational expressions instead of a `template_method` call.
16990///
16991/// The recovery uses only grammar fields: the selected field must be the left
16992/// side of `<`, that expression must be the left side of `>`, and the right
16993/// side of `>` must be the parenthesized call arguments. Semantic callers must
16994/// additionally prove the receiver owner and the member's template status.
16995pub fn recovered_relational_template_member_call(
16996    field: Node<'_>,
16997) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
16998    if field.kind() != "field_expression" {
16999        return None;
17000    }
17001    let receiver = field
17002        .child_by_field_name("argument")
17003        .or_else(|| field.child_by_field_name("object"))?;
17004    let member = field.child_by_field_name("field")?;
17005    let less = field.parent()?;
17006    if less.kind() != "binary_expression"
17007        || less.child_by_field_name("left") != Some(field)
17008        || less
17009            .child_by_field_name("operator")
17010            .is_none_or(|operator| operator.kind() != "<")
17011        || less.child_by_field_name("right").is_none()
17012    {
17013        return None;
17014    }
17015    let greater = less.parent()?;
17016    if greater.kind() != "binary_expression"
17017        || greater.child_by_field_name("left") != Some(less)
17018        || greater
17019            .child_by_field_name("operator")
17020            .is_none_or(|operator| operator.kind() != ">")
17021    {
17022        return None;
17023    }
17024    let arguments = greater.child_by_field_name("right")?;
17025    if arguments.kind() != "parenthesized_expression" {
17026        return None;
17027    }
17028    let arity = parenthesized_call_argument_arity(arguments)?;
17029    Some(RecoveredRelationalTemplateMemberCall {
17030        receiver,
17031        member,
17032        arity,
17033    })
17034}
17035
17036fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
17037    let expression = arguments.named_child(0)?;
17038    if expression.kind() != "comma_expression" {
17039        return Some(1);
17040    }
17041    let mut arity = 0usize;
17042    let mut stack = vec![expression];
17043    while let Some(node) = stack.pop() {
17044        if node.kind() == "comma_expression" {
17045            stack.push(node.child_by_field_name("right")?);
17046            stack.push(node.child_by_field_name("left")?);
17047        } else {
17048            arity += 1;
17049        }
17050    }
17051    Some(arity)
17052}
17053
17054/// Whether `node` is part of a call's callee expression, walking only through
17055/// the grammar wrappers that can structurally contain that callee.
17056pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
17057    while let Some(parent) = node.parent() {
17058        match parent.kind() {
17059            "call_expression" => {
17060                return parent
17061                    .child_by_field_name("function")
17062                    .or_else(|| parent.named_child(0))
17063                    == Some(node);
17064            }
17065            "qualified_identifier"
17066            | "scoped_identifier"
17067            | "template_function"
17068            | "template_type"
17069            | "field_expression" => node = parent,
17070            _ => return false,
17071        }
17072    }
17073    false
17074}
17075
17076pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
17077    if is_call_callee_node(node) {
17078        function_terminal_node(node)
17079    } else {
17080        node
17081    }
17082}
17083
17084pub fn normalize_type_text(value: &str) -> String {
17085    strip_tag_type_prefix(
17086        normalize_cpp_whitespace(value)
17087            .trim_start_matches("const ")
17088            .trim_end_matches('*')
17089            .trim_end_matches('&')
17090            .trim(),
17091    )
17092    .to_string()
17093}
17094
17095fn strip_tag_type_prefix(value: &str) -> &str {
17096    let value = value.trim_start_matches("const ");
17097    value
17098        .strip_prefix("struct ")
17099        .or_else(|| value.strip_prefix("class "))
17100        .or_else(|| value.strip_prefix("enum "))
17101        .unwrap_or(value)
17102        .trim()
17103}
17104
17105pub fn normalize_reference_name(value: &str) -> Option<String> {
17106    let normalized = normalize_cpp_reference_text(value);
17107    (!normalized.is_empty()).then_some(normalized)
17108}
17109
17110pub fn normalize_cpp_reference_text(value: &str) -> String {
17111    let mut text = normalize_cpp_whitespace(value)
17112        .trim_start_matches("new ")
17113        .trim()
17114        .to_string();
17115    if let Some(index) = text.find(['(', '{']) {
17116        text.truncate(index);
17117    }
17118    if let Some(index) = text.find('<') {
17119        text.truncate(index);
17120    }
17121    let normalized = text
17122        .trim()
17123        .trim_start_matches("const ")
17124        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
17125        .trim_matches(':')
17126        .trim();
17127    strip_tag_type_prefix(normalized).to_string()
17128}
17129
17130pub fn cpp_name_for(unit: &CodeUnit) -> String {
17131    let short = unit.short_name().replace(['.', '$'], "::");
17132    if unit.package_name().is_empty() {
17133        short
17134    } else {
17135        format!("{}::{}", unit.package_name(), short)
17136    }
17137}
17138
17139/// Render an indexed C++ qualified name from its authoritative FqName
17140/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
17141/// that belong to a template argument (for example `Args...`).
17142fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
17143    let fq = unit.fq();
17144    if fq.is_empty() {
17145        return None;
17146    }
17147    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17148    Some(
17149        fq.segments()
17150            .iter()
17151            .map(|&segment| interner.resolve(segment).0)
17152            .collect::<Vec<_>>()
17153            .join("::"),
17154    )
17155}
17156
17157fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
17158    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
17159        || unit.fq().is_empty() && cpp_name_for(unit) == expected
17160}
17161
17162/// Return the indexed C++ owner scope without reparsing its rendered name.
17163///
17164/// Template spellings are opaque within an indexed `FqName` segment.  In
17165/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
17166/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
17167/// through `parse_symbol_path` would mistake those dots for component
17168/// separators.  Cache-loaded/legacy units may still have an empty structured
17169/// name, so retain the parser only as that explicit fallback.
17170pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
17171    let fq = unit.fq();
17172    if !fq.is_empty() {
17173        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17174        let scope = fq
17175            .segments()
17176            .iter()
17177            .filter_map(|&segment| {
17178                let (text, kind) = interner.resolve(segment);
17179                matches!(
17180                    kind,
17181                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
17182                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
17183                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
17184                )
17185                .then(|| text.to_string())
17186            })
17187            .collect();
17188        return scope;
17189    }
17190    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17191        brokk_bifrost_core::analyzer::Language::Cpp,
17192        &cpp_name_for(unit),
17193    )
17194}
17195
17196// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
17197// (not the substring "->"), which deliberately reduces an `operator->`-style
17198// terminal segment to an empty tail rather than keeping it intact; the shared
17199// structured splitter's cpp operator-token merge would keep `operator->`
17200// whole instead, changing this function's result — `name_matches_callable`'s
17201// `expected.starts_with("operator")` fallback exists specifically to
17202// compensate for that reduction, and a pinned regression test
17203// (`operator-> must not be reduced with terminal_name-style punctuation
17204// splitting`) asserts today's char-class behavior. Not equivalence-provable;
17205// revisit alongside that pinned test if it is ever relaxed.
17206pub fn terminal_name(value: &str) -> &str {
17207    value
17208        .rsplit("::")
17209        .next()
17210        .unwrap_or(value)
17211        .rsplit(['.', '-', '>'])
17212        .next()
17213        .unwrap_or(value)
17214        .trim()
17215}
17216
17217pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
17218    terminal_name(&normalize_cpp_reference_text(value)) == expected
17219}
17220
17221pub fn name_matches_callable(value: &str, expected: &str) -> bool {
17222    name_matches_terminal(value, expected)
17223        || expected.starts_with("operator")
17224            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
17225}
17226
17227pub fn name_mentions(value: &str, expected: &str) -> bool {
17228    normalize_cpp_reference_text(value)
17229        .split("::")
17230        .any(|part| part == expected)
17231}
17232
17233pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
17234    let cpp_name = cpp_name_for(unit);
17235    if reference.contains("::") {
17236        return reference == cpp_name;
17237    }
17238    reference == cpp_name
17239        || terminal_name(reference) == unit.identifier()
17240            && (unit.package_name().is_empty() || reference == unit.identifier())
17241}
17242
17243pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
17244    match kind {
17245        TargetKind::Type
17246        | TargetKind::Constructor
17247        | TargetKind::Method
17248        | TargetKind::MemberField => true,
17249        TargetKind::FreeFunction => unit.is_function(),
17250        TargetKind::GlobalField => unit.is_field(),
17251        TargetKind::Macro => unit.is_macro(),
17252    }
17253}
17254
17255pub fn is_type_alias(unit: &CodeUnit) -> bool {
17256    unit.kind() == CodeUnitType::Field
17257        && unit.signature().is_some_and(|signature| {
17258            signature.starts_with("typedef ") || signature.starts_with("using ")
17259        })
17260}
17261
17262fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
17263    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
17264    let target_name = cpp_name_for(target);
17265    if normalized.contains("::") {
17266        return normalized == target_name;
17267    }
17268    if let Some(namespace) = alias.namespace.as_deref() {
17269        return namespace_prefixes(namespace)
17270            .into_iter()
17271            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
17272    }
17273    target.package_name().is_empty() && normalized == target.identifier()
17274}
17275
17276/// The declared return type text of a C++ function unit, with leading declaration specifiers
17277/// stripped, e.g. `T*` for `T* operator->()`.
17278pub fn cpp_function_return_type_text(
17279    analyzer: &CppGraphSource<'_>,
17280    function: &CodeUnit,
17281) -> Option<String> {
17282    let metadata = analyzer.signature_metadata(function);
17283    if !metadata.is_empty() {
17284        let first = metadata.first()?.return_type_text()?;
17285        return metadata
17286            .iter()
17287            .all(|metadata| metadata.return_type_text() == Some(first))
17288            .then(|| first.to_string());
17289    }
17290    let signature = cpp_function_signature_text(analyzer, function)?;
17291    cpp_function_return_type_text_from_signature(&signature)
17292}
17293
17294fn cpp_function_signature_text(
17295    analyzer: &CppGraphSource<'_>,
17296    function: &CodeUnit,
17297) -> Option<String> {
17298    function
17299        .signature()
17300        .filter(|signature| signature.contains(function.identifier()))
17301        .map(str::to_string)
17302        .or_else(|| analyzer.signatures(function).first().cloned())
17303        .or_else(|| analyzer.get_source(function, false))
17304}
17305
17306fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
17307    let open = signature.find('(')?;
17308    let name_at = cpp_function_name_start(signature, open)?;
17309    if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
17310        return Some(return_type);
17311    }
17312    let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
17313        .split_whitespace()
17314        .filter(|token| {
17315            !matches!(
17316                *token,
17317                "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
17318            )
17319        })
17320        .collect::<Vec<_>>()
17321        .join(" ");
17322    let type_text = type_text.trim();
17323    (!type_text.is_empty()).then(|| type_text.to_string())
17324}
17325
17326fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
17327    let before_parameters = &signature[..open];
17328    if let Some(operator_at) = before_parameters.rfind("operator") {
17329        let boundary = operator_at == 0
17330            || before_parameters[..operator_at]
17331                .chars()
17332                .next_back()
17333                .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
17334        if boundary {
17335            return Some(operator_at);
17336        }
17337    }
17338    before_parameters
17339        .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
17340        .map(|index| index + 1)
17341}
17342
17343fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
17344    let open = signature_from_name.find('(')?;
17345    let mut depth = 0i32;
17346    for (offset, ch) in signature_from_name[open..].char_indices() {
17347        match ch {
17348            '(' => depth += 1,
17349            ')' => {
17350                depth -= 1;
17351                if depth == 0 {
17352                    let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
17353                    let arrow = rest.find("->")?;
17354                    let return_type = rest[arrow + 2..].trim_start();
17355                    let return_type = return_type
17356                        .split(['{', ';'])
17357                        .next()
17358                        .unwrap_or(return_type)
17359                        .trim();
17360                    return (!return_type.is_empty()).then(|| return_type.to_string());
17361                }
17362            }
17363            _ => {}
17364        }
17365    }
17366    None
17367}
17368
17369/// Strip a leading `template <...>` parameter clause, leaving the declaration that follows.
17370/// Returns the input unchanged when there is no such clause.
17371fn cpp_strip_leading_template_clause(text: &str) -> &str {
17372    let trimmed = text.trim_start();
17373    let Some(rest) = trimmed.strip_prefix("template") else {
17374        return text;
17375    };
17376    let rest = rest.trim_start();
17377    if !rest.starts_with('<') {
17378        return text;
17379    }
17380    let mut depth = 0i32;
17381    for (offset, ch) in rest.char_indices() {
17382        match ch {
17383            '<' => depth += 1,
17384            '>' => {
17385                depth -= 1;
17386                if depth == 0 {
17387                    return rest[offset + ch.len_utf8()..].trim_start();
17388                }
17389            }
17390            _ => {}
17391        }
17392    }
17393    text
17394}
17395
17396pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
17397    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
17398    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
17399    // the same string `default_parent_fq_name`/`fq().parent()` would render:
17400    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
17401    // `::`) between a trailing `Package` segment and a following `Type`
17402    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
17403    // popping the unit's own `fq()` segment would NOT reproduce this
17404    // fully-`::`-joined string. Left as a split on the locally-built
17405    // all-colon string rather than the unit's structured name.
17406    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
17407        namespace
17408            .strip_prefix("anonymous_namespace::")
17409            .unwrap_or(namespace)
17410            .to_string()
17411    })
17412}
17413
17414fn namespace_prefixes(namespace: &str) -> Vec<String> {
17415    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
17416    // non-`::` separator already converted to `::`, so re-tokenizing it with
17417    // the shared structured splitter and progressively popping the last
17418    // component reproduces the `rsplit_once("::")` outward walk exactly (same
17419    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
17420    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17421        brokk_bifrost_core::analyzer::Language::Cpp,
17422        namespace,
17423    );
17424    let mut prefixes = Vec::new();
17425    while !parts.is_empty() {
17426        prefixes.push(parts.join("::"));
17427        parts.pop();
17428    }
17429    prefixes
17430}
17431
17432fn nearest_namespace_candidates(
17433    candidates: Vec<CodeUnit>,
17434    normalized: &str,
17435    lexical_namespace: Option<&str>,
17436) -> Vec<CodeUnit> {
17437    if normalized.contains("::") {
17438        return candidates;
17439    }
17440    if let Some(namespace) = lexical_namespace {
17441        for prefix in namespace_prefixes(namespace) {
17442            let scoped = candidates
17443                .iter()
17444                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
17445                .cloned()
17446                .collect::<Vec<_>>();
17447            if !scoped.is_empty() {
17448                return scoped;
17449            }
17450        }
17451    }
17452    candidates
17453        .into_iter()
17454        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
17455        .collect()
17456}
17457
17458pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
17459    let mut namespaces = Vec::new();
17460    let mut current = node.parent();
17461    while let Some(parent) = current {
17462        if parent.kind() == "namespace_definition"
17463            && let Some(name) = parent.child_by_field_name("name")
17464        {
17465            let namespace = normalize_cpp_reference_text(node_text(name, source));
17466            if !namespace.is_empty() {
17467                namespaces.push(namespace);
17468            }
17469        }
17470        current = parent.parent();
17471    }
17472    if namespaces.is_empty() {
17473        None
17474    } else {
17475        namespaces.reverse();
17476        Some(namespaces.join("::"))
17477    }
17478}
17479
17480/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
17481/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
17482/// globals rather than members.
17483pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17484    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
17485}
17486
17487fn type_owner_resolution(
17488    analyzer: &CppGraphSource<'_>,
17489    code_unit: &CodeUnit,
17490) -> Option<ResolvedTypeOwner> {
17491    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
17492}
17493
17494fn target_type_owner_resolution(
17495    analyzer: &CppGraphSource<'_>,
17496    code_unit: &CodeUnit,
17497) -> Option<ResolvedTypeOwner> {
17498    match type_owner_resolution(analyzer, code_unit) {
17499        Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
17500        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
17501    }
17502}
17503
17504/// Recover method identity for an indexed out-of-line definition when the
17505/// ordinary parent edge is absent. Prefer the unique include-visible forward
17506/// declaration, then classify exact-FQN class declarations elsewhere in the
17507/// workspace. A unique complete declaration wins; otherwise multiple forward
17508/// declarations are one owner only when they all share one logical identity.
17509/// The qualified callable FQN proves that owner spelling even when its defining
17510/// header is outside the scan file's include closure, while unknown or competing
17511/// complete declarations remain ambiguous.
17512/// This is deliberately target-only: canonical declaration resolution must
17513/// continue to prefer the callable definition rather than replacing it with
17514/// the recovered owner.
17515fn target_forward_owner_resolution(
17516    analyzer: &CppGraphSource<'_>,
17517    code_unit: &CodeUnit,
17518) -> Option<ResolvedTypeOwner> {
17519    if !code_unit.is_function() {
17520        return None;
17521    }
17522    // A top-level free function has no owner at all, and `FqName::parent`
17523    // answers the empty name rather than `None` for a one-segment identity.
17524    // `default_parent_fq_name`, which this replaced, filtered that case out;
17525    // asking the relational store for the empty name is a batch error that
17526    // fails the whole target frontier.
17527    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
17528    let cpp = analyzer.cpp?;
17529    let mut visible_files = HashSet::default();
17530    collect_include_closure(
17531        analyzer,
17532        cpp.include_target_index(),
17533        code_unit.source(),
17534        &mut visible_files,
17535        None,
17536    );
17537    let candidates = analyzer.workspace_definitions().exact(&owner_name);
17538    let visible_candidates = candidates
17539        .iter()
17540        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17541        .cloned()
17542        .collect::<Vec<_>>();
17543    match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17544        DirectOwnerResolution::UniqueFull(unit) => {
17545            return Some(ResolvedTypeOwner {
17546                unit,
17547                is_forward_declaration: false,
17548            });
17549        }
17550        DirectOwnerResolution::ForwardsOnly(forwards) => {
17551            return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17552                unit: forwards.into_iter().next().unwrap(),
17553                is_forward_declaration: true,
17554            });
17555        }
17556        DirectOwnerResolution::Ambiguous => return None,
17557        DirectOwnerResolution::None => {}
17558    }
17559
17560    let candidates = candidates
17561        .into_iter()
17562        .filter(|candidate| candidate.is_class())
17563        .collect::<Vec<_>>();
17564    let (unit, is_forward_declaration) =
17565        match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17566            DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17567            DirectOwnerResolution::ForwardsOnly(forwards) => {
17568                (unique_logical_forward_owner(forwards)?, true)
17569            }
17570            DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17571        };
17572    Some(ResolvedTypeOwner {
17573        unit,
17574        is_forward_declaration,
17575    })
17576}
17577
17578pub fn precise_parent_of(
17579    analyzer: &CppGraphSource<'_>,
17580    visibility: &VisibilityIndex<'_>,
17581    code_unit: &CodeUnit,
17582) -> Option<CodeUnit> {
17583    visibility.cached_precise_parent_of(analyzer, code_unit)
17584}
17585
17586fn precise_parent_resolution(
17587    analyzer: &CppGraphSource<'_>,
17588    code_unit: &CodeUnit,
17589) -> Option<ResolvedTypeOwner> {
17590    #[cfg(any(test, feature = "test-support"))]
17591    if let Some(cpp) = analyzer.cpp {
17592        cpp.record_cpp_parent_resolution_for_test();
17593    }
17594    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17595        return Some(ResolvedTypeOwner {
17596            unit,
17597            is_forward_declaration: false,
17598        });
17599    }
17600    let fallback = analyzer.parent_of(code_unit);
17601    if !code_unit.owner_is_type_scope() {
17602        return fallback.map(|unit| ResolvedTypeOwner {
17603            unit,
17604            is_forward_declaration: false,
17605        });
17606    }
17607    let owner_fq = code_unit
17608        .fq()
17609        .parent()
17610        .expect("a unit with an owner identifier has a structured parent");
17611    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17612    match same_source_owner(analyzer, code_unit, &owner_candidates) {
17613        DirectOwnerResolution::UniqueFull(owner) => {
17614            return Some(ResolvedTypeOwner {
17615                unit: owner,
17616                is_forward_declaration: false,
17617            });
17618        }
17619        DirectOwnerResolution::Ambiguous => return None,
17620        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17621    }
17622    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17623        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17624            unit: owner,
17625            is_forward_declaration: false,
17626        }),
17627        DirectOwnerResolution::Ambiguous => None,
17628        DirectOwnerResolution::ForwardsOnly(forwards) => {
17629            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17630                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17631                    unit: owner,
17632                    is_forward_declaration: false,
17633                }),
17634                FullOwnerResolution::None => {
17635                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17636                        unit,
17637                        is_forward_declaration: true,
17638                    })
17639                }
17640                FullOwnerResolution::Ambiguous => None,
17641            }
17642        }
17643        DirectOwnerResolution::None => {
17644            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17645                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17646                    unit: owner,
17647                    is_forward_declaration: false,
17648                }),
17649                FullOwnerResolution::Ambiguous => None,
17650                FullOwnerResolution::None => fallback
17651                    .filter(|parent| {
17652                        parent.source() == code_unit.source()
17653                            && parent.fq() == &owner_fq
17654                            && (!parent.is_class()
17655                                || cpp_class_declaration_strength(analyzer, parent)
17656                                    == CppClassDeclarationStrength::Full)
17657                    })
17658                    .map(|unit| ResolvedTypeOwner {
17659                        unit,
17660                        is_forward_declaration: false,
17661                    }),
17662            }
17663        }
17664    }
17665}
17666
17667fn exact_structural_type_parent(
17668    analyzer: &CppGraphSource<'_>,
17669    code_unit: &CodeUnit,
17670) -> Option<CodeUnit> {
17671    if !code_unit.is_function() && !code_unit.is_field() {
17672        return None;
17673    }
17674    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
17675    let cpp = analyzer.cpp?;
17676    let parent = cpp.structural_parent_of(code_unit)?;
17677    (!parent.is_module()
17678        && parent.source() == code_unit.source()
17679        && parent.package_name() == code_unit.package_name()
17680        && parent.short_name() == encoded_owner)
17681        .then_some(parent)
17682}
17683
17684fn same_source_owner(
17685    analyzer: &CppGraphSource<'_>,
17686    code_unit: &CodeUnit,
17687    owner_candidates: &[CodeUnit],
17688) -> DirectOwnerResolution {
17689    let candidates = owner_candidates
17690        .iter()
17691        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
17692        .cloned()
17693        .collect::<Vec<_>>();
17694    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17695    classify_direct_owner_candidates(analyzer, candidates.into_iter())
17696}
17697
17698fn visible_full_cpp_owner(
17699    analyzer: &CppGraphSource<'_>,
17700    code_unit: &CodeUnit,
17701    owner_candidates: &[CodeUnit],
17702) -> FullOwnerResolution {
17703    let Some(cpp) = analyzer.cpp else {
17704        return FullOwnerResolution::None;
17705    };
17706    let mut visible_files = HashSet::default();
17707    collect_include_closure(
17708        analyzer,
17709        cpp.include_target_index(),
17710        code_unit.source(),
17711        &mut visible_files,
17712        None,
17713    );
17714    let candidates = owner_candidates
17715        .iter()
17716        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17717        .cloned()
17718        .collect::<Vec<_>>();
17719    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17720    let mut full_definition = None;
17721    for candidate in candidates {
17722        match cpp_class_declaration_strength(analyzer, &candidate) {
17723            CppClassDeclarationStrength::Full if full_definition.is_some() => {
17724                return FullOwnerResolution::Ambiguous;
17725            }
17726            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17727            CppClassDeclarationStrength::Forward => {}
17728            CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
17729        }
17730    }
17731    full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
17732}
17733
17734pub enum DirectOwnerResolution {
17735    None,
17736    ForwardsOnly(Vec<CodeUnit>),
17737    UniqueFull(CodeUnit),
17738    Ambiguous,
17739}
17740
17741enum FullOwnerResolution {
17742    None,
17743    Unique(CodeUnit),
17744    Ambiguous,
17745}
17746
17747#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17748pub enum CppClassDeclarationStrength {
17749    Full,
17750    Forward,
17751    Unknown,
17752}
17753
17754fn directly_included_owner(
17755    analyzer: &CppGraphSource<'_>,
17756    code_unit: &CodeUnit,
17757    owner_candidates: &[CodeUnit],
17758) -> DirectOwnerResolution {
17759    let Some(cpp) = analyzer.cpp else {
17760        return DirectOwnerResolution::None;
17761    };
17762    let imports = analyzer.import_statements(code_unit.source());
17763    let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
17764        .into_iter()
17765        .flat_map(|include| {
17766            resolve_include_targets_with_index(
17767                code_unit.source(),
17768                &include,
17769                cpp.include_target_index(),
17770            )
17771        })
17772        .collect();
17773    let candidates = owner_candidates
17774        .iter()
17775        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
17776        .cloned()
17777        .collect::<Vec<_>>();
17778    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17779    classify_direct_owner_candidates(analyzer, candidates.into_iter())
17780}
17781
17782fn prefer_member_declaring_owners(
17783    analyzer: &CppGraphSource<'_>,
17784    member: &CodeUnit,
17785    candidates: Vec<CodeUnit>,
17786) -> Vec<CodeUnit> {
17787    let matching = candidates
17788        .iter()
17789        .filter(|owner| owner_declares_member(analyzer, owner, member))
17790        .cloned()
17791        .collect::<Vec<_>>();
17792    if matching.is_empty() {
17793        candidates
17794    } else {
17795        matching
17796    }
17797}
17798
17799fn owner_declares_member(
17800    analyzer: &CppGraphSource<'_>,
17801    owner: &CodeUnit,
17802    member: &CodeUnit,
17803) -> bool {
17804    analyzer.direct_children(owner).into_iter().any(|child| {
17805        child.kind() == member.kind()
17806            && child.identifier() == member.identifier()
17807            && child.signature() == member.signature()
17808    })
17809}
17810
17811fn classify_direct_owner_candidates(
17812    analyzer: &CppGraphSource<'_>,
17813    candidates: impl Iterator<Item = CodeUnit>,
17814) -> DirectOwnerResolution {
17815    collapse_owner_candidates(candidates.map(|candidate| {
17816        let strength = cpp_class_declaration_strength(analyzer, &candidate);
17817        (candidate, strength)
17818    }))
17819}
17820
17821pub fn collapse_owner_candidates(
17822    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
17823) -> DirectOwnerResolution {
17824    let mut full_definition = None;
17825    let mut forwards = Vec::new();
17826    for (candidate, strength) in candidates {
17827        match strength {
17828            CppClassDeclarationStrength::Full if full_definition.is_some() => {
17829                return DirectOwnerResolution::Ambiguous;
17830            }
17831            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17832            CppClassDeclarationStrength::Forward => forwards.push(candidate),
17833            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
17834        }
17835    }
17836    if let Some(owner) = full_definition {
17837        DirectOwnerResolution::UniqueFull(owner)
17838    } else if !forwards.is_empty() {
17839        DirectOwnerResolution::ForwardsOnly(forwards)
17840    } else {
17841        DirectOwnerResolution::None
17842    }
17843}
17844
17845#[cfg(any(test, feature = "test-support"))]
17846pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17847    unique_logical_forward_owner(forwards)
17848}
17849
17850fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17851    let first = forwards.pop()?;
17852    forwards
17853        .iter()
17854        .all(|forward| same_logical_symbol(forward, &first))
17855        .then_some(first)
17856}
17857
17858pub fn cpp_class_declaration_strength(
17859    analyzer: &CppGraphSource<'_>,
17860    candidate: &CodeUnit,
17861) -> CppClassDeclarationStrength {
17862    // The answer is a pure function of the unit's ranges and its file's tree,
17863    // and the inverse scan asks it once per declaration seed. On a translation
17864    // unit the parser could not fully recover, each ask re-derives the
17865    // export-macro recovery shapes from the file's `ERROR` subtrees, so without
17866    // this memo one file's scan is quadratic in its own size: 97% of Catch2's
17867    // 284 s inverse scan of `extras/catch_amalgamated.cpp` was in this call
17868    // (#1496).
17869    let Some(cpp) = analyzer.cpp else {
17870        return uncached_cpp_class_declaration_strength(analyzer, candidate);
17871    };
17872    if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
17873        return strength;
17874    }
17875    let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
17876    cpp.cache_class_declaration_strength(candidate, strength);
17877    strength
17878}
17879
17880fn uncached_cpp_class_declaration_strength(
17881    analyzer: &CppGraphSource<'_>,
17882    candidate: &CodeUnit,
17883) -> CppClassDeclarationStrength {
17884    if let Some(cpp) = analyzer.cpp
17885        && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
17886    {
17887        return cpp_class_declaration_strength_in_tree(
17888            analyzer,
17889            &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
17890            candidate,
17891            prepared.source(),
17892            prepared.tree().root_node(),
17893        );
17894    }
17895    let Some(source) = analyzer.indexed_source(candidate.source()) else {
17896        return CppClassDeclarationStrength::Unknown;
17897    };
17898    #[cfg(any(test, feature = "test-support"))]
17899    if let Some(cpp) = analyzer.cpp {
17900        cpp.record_cpp_class_strength_parse_for_test();
17901    }
17902    let mut parser = Parser::new();
17903    if parser
17904        .set_language(&tree_sitter_cpp::LANGUAGE.into())
17905        .is_err()
17906    {
17907        return CppClassDeclarationStrength::Unknown;
17908    }
17909    let Some(tree) = parser.parse(&source, None) else {
17910        return CppClassDeclarationStrength::Unknown;
17911    };
17912    // This branch reparses a file the analyzer has no prepared tree for, so its
17913    // recovery index is that one tree's and cannot be shared.
17914    let recovered_export_classes =
17915        CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
17916    cpp_class_declaration_strength_in_tree(
17917        analyzer,
17918        &recovered_export_classes,
17919        candidate,
17920        &source,
17921        tree.root_node(),
17922    )
17923}
17924
17925fn cpp_class_declaration_strength_in_tree(
17926    analyzer: &CppGraphSource<'_>,
17927    recovered_export_classes: &CppRecoveredExportClassIndex,
17928    candidate: &CodeUnit,
17929    source: &str,
17930    root: Node<'_>,
17931) -> CppClassDeclarationStrength {
17932    let ranges = analyzer.ranges(candidate);
17933    let mut saw_forward = false;
17934    for range in ranges {
17935        // The recovered export-macro shapes answer for their own ranges; only a
17936        // range no recovery claims is read as a plain specifier.
17937        match recovered_class_body_at(
17938            recovered_export_classes,
17939            root,
17940            source,
17941            candidate.identifier(),
17942            &range,
17943        ) {
17944            Some(true) => return CppClassDeclarationStrength::Full,
17945            Some(false) => {
17946                saw_forward = true;
17947                continue;
17948            }
17949            None => {}
17950        }
17951        // Only a node covering the range's start byte can be the specifier for
17952        // this range, so apply that test where nodes enter the stack rather
17953        // than where they leave it. Pushing first meant one ask enqueued every
17954        // sibling at every level it descended, which on a translation unit with
17955        // thousands of top-level declarations is a per-ask cost proportional to
17956        // the file (#1496).
17957        let covers_range_start = |node: &Node<'_>| {
17958            node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
17959        };
17960        let mut stack = Vec::new();
17961        if covers_range_start(&root) {
17962            stack.push(root);
17963        }
17964        while let Some(node) = stack.pop() {
17965            if node.start_byte() == range.start_byte
17966                && node.end_byte() == range.end_byte
17967                && matches!(
17968                    node.kind(),
17969                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
17970                )
17971            {
17972                if cpp_class_node_has_body(node) {
17973                    return CppClassDeclarationStrength::Full;
17974                }
17975                saw_forward = true;
17976            }
17977            let mut cursor = node.walk();
17978            stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
17979        }
17980    }
17981    if saw_forward {
17982        CppClassDeclarationStrength::Forward
17983    } else {
17984        CppClassDeclarationStrength::Unknown
17985    }
17986}
17987
17988fn cpp_class_node_has_body(node: Node<'_>) -> bool {
17989    node.child_by_field_name("body").is_some() || {
17990        let mut cursor = node.walk();
17991        node.named_children(&mut cursor).any(|child| {
17992            matches!(
17993                child.kind(),
17994                "declaration_list" | "field_declaration_list" | "enumerator_list"
17995            )
17996        })
17997    }
17998}
17999
18000#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18001enum CppCTagKind {
18002    Struct,
18003    Union,
18004}
18005
18006fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
18007    let declaration = analyzer.get_source(code_unit, false)?;
18008    let mut parser = Parser::new();
18009    parser
18010        .set_language(&tree_sitter_cpp::LANGUAGE.into())
18011        .ok()?;
18012    let tree = parser.parse(&declaration, None)?;
18013    let mut stack = vec![tree.root_node()];
18014    while let Some(node) = stack.pop() {
18015        let kind = match node.kind() {
18016            "struct_specifier" => CppCTagKind::Struct,
18017            "union_specifier" => CppCTagKind::Union,
18018            _ => {
18019                let mut cursor = node.walk();
18020                stack.extend(node.named_children(&mut cursor));
18021                continue;
18022            }
18023        };
18024        if node
18025            .child_by_field_name("name")
18026            .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
18027        {
18028            return Some(kind);
18029        }
18030        let mut cursor = node.walk();
18031        stack.extend(node.named_children(&mut cursor));
18032    }
18033    None
18034}
18035
18036pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18037    if !code_unit.owner_is_type_scope() {
18038        return None;
18039    }
18040    let owner_fq = code_unit.fq().parent()?;
18041    ctx.analyzer
18042        .workspace_definitions()
18043        .exact(&owner_fq)
18044        .into_iter()
18045        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
18046}
18047
18048pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18049    left.kind() == right.kind()
18050        && left.fq_name() == right.fq_name()
18051        && left.signature() == right.signature()
18052        && left.source() == right.source()
18053}
18054
18055pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18056    same_symbol(left, right) || same_logical_symbol(left, right)
18057}
18058
18059pub fn same_visible_global_field_symbol(
18060    analyzer: &CppGraphSource<'_>,
18061    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
18062    left: &CodeUnit,
18063    right: &CodeUnit,
18064) -> bool {
18065    if same_symbol(left, right) {
18066        return true;
18067    }
18068    if !same_logical_symbol(left, right) {
18069        return false;
18070    }
18071    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
18072        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
18073    {
18074        left.source() == right.source()
18075    } else {
18076        true
18077    }
18078}
18079
18080fn cpp_global_field_has_internal_linkage_cached(
18081    analyzer: &CppGraphSource<'_>,
18082    cache: &mut HashMap<CodeUnit, bool>,
18083    candidate: &CodeUnit,
18084) -> bool {
18085    if let Some(internal) = cache.get(candidate) {
18086        return *internal;
18087    }
18088    #[cfg(any(test, feature = "test-support"))]
18089    note_cpp_global_field_internal_linkage_classification_for_test();
18090    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
18091    cache.insert(candidate.clone(), internal);
18092    internal
18093}
18094
18095#[cfg(any(test, feature = "test-support"))]
18096thread_local! {
18097    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18098}
18099
18100#[cfg(any(test, feature = "test-support"))]
18101fn note_cpp_global_field_internal_linkage_classification_for_test() {
18102    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18103        count.set(count.get() + 1);
18104    });
18105}
18106
18107#[cfg(any(test, feature = "test-support"))]
18108pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
18109    body: impl FnOnce() -> T,
18110) -> (T, usize) {
18111    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18112        count.set(0);
18113        let result = body();
18114        let observed = count.get();
18115        count.set(0);
18116        (result, observed)
18117    })
18118}
18119
18120pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18121    left.kind() == right.kind()
18122        && left.fq_name() == right.fq_name()
18123        && left.signature() == right.signature()
18124}
18125
18126pub fn cpp_global_field_has_internal_linkage(
18127    analyzer: &CppGraphSource<'_>,
18128    candidate: &CodeUnit,
18129) -> bool {
18130    if !candidate.is_field() || candidate.short_name().contains('.') {
18131        return false;
18132    }
18133    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
18134        return false;
18135    };
18136    match local_linkage {
18137        CppFieldLinkage::Internal => true,
18138        CppFieldLinkage::External => false,
18139        CppFieldLinkage::InternalUnlessExternalPeer => {
18140            !cpp_global_field_linkage_peers(analyzer, candidate)
18141                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
18142                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
18143        }
18144    }
18145}
18146
18147fn cpp_global_field_linkage_peers<'a>(
18148    analyzer: &CppGraphSource<'a>,
18149    candidate: &'a CodeUnit,
18150) -> impl Iterator<Item = CodeUnit> + 'a {
18151    let name = candidate.fq().clone();
18152    analyzer
18153        .workspace_definitions()
18154        .exact(&name)
18155        .into_iter()
18156        .filter(move |peer| {
18157            if peer == candidate {
18158                return false;
18159            }
18160            #[cfg(any(test, feature = "test-support"))]
18161            note_cpp_global_field_linkage_peer_inspection_for_test();
18162            same_logical_symbol(peer, candidate)
18163        })
18164}
18165
18166#[cfg(any(test, feature = "test-support"))]
18167thread_local! {
18168    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18169}
18170
18171#[cfg(any(test, feature = "test-support"))]
18172fn note_cpp_global_field_linkage_peer_inspection_for_test() {
18173    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
18174        count.set(count.get() + 1);
18175    });
18176}
18177
18178#[cfg(any(test, feature = "test-support"))]
18179pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
18180    body: impl FnOnce() -> T,
18181) -> (T, usize) {
18182    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
18183        count.set(0);
18184        let result = body();
18185        let observed = count.get();
18186        count.set(0);
18187        (result, observed)
18188    })
18189}
18190
18191fn cpp_global_field_declaration_linkage(
18192    analyzer: &CppGraphSource<'_>,
18193    candidate: &CodeUnit,
18194) -> Option<CppFieldLinkage> {
18195    if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
18196        return Some(linkage);
18197    }
18198    let cpp = analyzer.cpp?;
18199    if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
18200        return cpp_global_field_declaration_linkage_in_tree(
18201            analyzer,
18202            candidate,
18203            prepared.source(),
18204            prepared.tree().root_node(),
18205        );
18206    }
18207    let source = analyzer.indexed_source(candidate.source())?;
18208    let mut parser = Parser::new();
18209    if parser
18210        .set_language(&tree_sitter_cpp::LANGUAGE.into())
18211        .is_err()
18212    {
18213        return None;
18214    }
18215    let tree = parser.parse(&source, None)?;
18216    cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
18217}
18218
18219fn cpp_global_field_declaration_linkage_in_tree(
18220    analyzer: &CppGraphSource<'_>,
18221    candidate: &CodeUnit,
18222    source: &str,
18223    root: Node<'_>,
18224) -> Option<CppFieldLinkage> {
18225    analyzer.ranges(candidate).iter().find_map(|range| {
18226        node_for_exact_range(root, range)
18227            .and_then(enclosing_cpp_field_declaration)
18228            .map(|declaration| {
18229                // One question about one declaration; see `ParentIndex::unindexed`.
18230                cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
18231            })
18232    })
18233}
18234
18235fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
18236    loop {
18237        if matches!(node.kind(), "declaration" | "field_declaration") {
18238            return Some(node);
18239        }
18240        node = node.parent()?;
18241    }
18242}
18243
18244#[cfg(test)]
18245mod tests {
18246    #[test]
18247    fn issue_3089_statement_formal_at_end_of_replacement() {
18248        let parameters = vec!["handle".to_owned(), "block".to_owned()];
18249        for replacement in [
18250            "do { header_event_t* event; if ((handle)->active) block } while (0)",
18251            "do { header_event_t* event; block } while (0)",
18252        ] {
18253            assert!(
18254                super::VisibilityIndex::parse_macro_replacement_body(replacement, &parameters)
18255                    .is_some(),
18256                "{replacement}"
18257            );
18258        }
18259    }
18260    use super::*;
18261
18262    #[test]
18263    fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
18264        let source = "int size(void) { return sizeof(((Payload))); }\n";
18265        let mut parser = Parser::new();
18266        parser
18267            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18268            .expect("C++ grammar");
18269        let tree = parser.parse(source, None).expect("fixture tree");
18270        let start = source.find("Payload").expect("sizeof operand");
18271        let node = tree
18272            .root_node()
18273            .named_descendant_for_byte_range(start, start + "Payload".len())
18274            .expect("focused operand");
18275        let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
18276        let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
18277
18278        assert_eq!(node.kind(), "identifier");
18279        assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
18280        assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
18281    }
18282
18283    fn parse_cpp(source: &str) -> Tree {
18284        let mut parser = Parser::new();
18285        parser
18286            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18287            .expect("C++ grammar");
18288        parser.parse(source, None).expect("fixture tree")
18289    }
18290
18291    fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
18292        let start = source.find(needle).expect("fixture needle");
18293        tree.root_node()
18294            .named_descendant_for_byte_range(start, start + needle.len())
18295            .expect("node at needle")
18296    }
18297
18298    fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
18299        let mut parser = Parser::new();
18300        parser
18301            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18302            .expect("C++ grammar");
18303        let tree = parser.parse(source, None).expect("fixture tree");
18304        PreparedSyntaxTree::new(
18305            PreparedSyntaxSource::Exact(Arc::from(source)),
18306            tree,
18307            compute_line_starts(source),
18308            LanguageDialect::Standard(Language::Cpp),
18309            PreparedSourceOrigin::Disk,
18310            None,
18311        )
18312    }
18313
18314    fn unresolved_include_before(source: &str, reference: &str) -> bool {
18315        let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
18316        let prepared = prepared_cpp(source);
18317        let facts = collect_structured_include_facts(&prepared);
18318        let include_targets = IncludeTargetIndex::build([&file]);
18319        has_unresolved_include_visible_before_in_prepared(
18320            &file,
18321            &prepared,
18322            &include_targets,
18323            &facts,
18324            source.find(reference).expect("reference fixture"),
18325        )
18326    }
18327
18328    #[test]
18329    fn unresolved_include_before_reference_is_visible() {
18330        let source = "#include \"missing.h\"\nint use = Missing;\n";
18331        assert!(unresolved_include_before(source, "Missing"));
18332    }
18333
18334    #[test]
18335    fn unresolved_include_after_reference_is_not_visible() {
18336        let source = "int use = Missing;\n#include \"missing.h\"\n";
18337        assert!(!unresolved_include_before(source, "Missing"));
18338    }
18339
18340    #[test]
18341    fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
18342        let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
18343        assert!(!unresolved_include_before(source, "Missing"));
18344    }
18345
18346    #[test]
18347    fn unresolved_include_in_current_branch_is_visible() {
18348        let source =
18349            "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
18350        assert!(unresolved_include_before(source, "Missing"));
18351    }
18352
18353    /// Two macro-decorated class heads make tree-sitter close `detail` at the
18354    /// first class's `}`, `matchers` at the second's, and `app` at `detail`'s
18355    /// real `}`; the tail parses at translation-unit level and the two real
18356    /// closes for `matchers` and `app` land in a trailing ERROR (#1537).
18357    const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
18358namespace matchers {
18359    namespace detail {
18360        class API [[nodiscard]] First {
18361        public:
18362            int value() const { return count_ + 1; }
18363        private:
18364            int count_;
18365        };
18366        class API [[nodiscard]] Second {
18367        public:
18368            int value() const { return count_ + 2; }
18369        private:
18370            int count_;
18371        };
18372    } // namespace detail
18373
18374    template <typename T>
18375    void tail_function(MatcherBase<T> const& value);
18376
18377    class TailClass {};
18378} // namespace matchers
18379} // namespace app
18380
18381struct AfterAll {};
18382"#;
18383
18384    #[test]
18385    fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
18386        let source = STOLEN_BRACE_CASCADE;
18387        let tree = parse_cpp(source);
18388        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18389
18390        let tail_class = named_node_at(&tree, source, "TailClass");
18391        assert!(
18392            !has_ancestor_kind(tail_class, "namespace_definition"),
18393            "the fixture must reproduce the recovery: the tail has no namespace ancestor"
18394        );
18395        let displaced = named_node_at(&tree, source, "Second");
18396        assert_eq!(
18397            enclosing_namespace_components(displaced, source),
18398            Some(vec!["app".to_string(), "matchers".to_string()]),
18399            "the fixture must displace the second class out of detail"
18400        );
18401
18402        let components = |needle: &str| {
18403            index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
18404        };
18405        assert_eq!(components("First"), ["app", "matchers", "detail"]);
18406        assert_eq!(components("Second"), ["app", "matchers", "detail"]);
18407        assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
18408        assert_eq!(components("tail_function"), ["app", "matchers"]);
18409        assert_eq!(components("TailClass"), ["app", "matchers"]);
18410        assert!(components("AfterAll").is_empty());
18411    }
18412
18413    #[test]
18414    fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
18415        let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
18416        let tree = parse_cpp(clean);
18417        assert!(!tree.root_node().has_error());
18418        assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
18419
18420        // A namespace that merely contains a parse error closes where its
18421        // brace says; the declarations after it keep their parsed scope.
18422        let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
18423        let tree = parse_cpp(damaged);
18424        assert!(tree.root_node().has_error());
18425        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
18426        assert_eq!(
18427            index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
18428            ["a"]
18429        );
18430    }
18431
18432    /// A function-like `#define` in a class body, whose replacement returns a
18433    /// SFINAE type, makes tree-sitter give up on the whole enclosing namespace:
18434    /// `namespace app {` becomes an `ERROR` whose own `namespace`, name and `{`
18435    /// tokens are the only trace of the head, and every declaration the
18436    /// namespace holds becomes their flat sibling. This is Catch2's
18437    /// `catch_decomposer.hpp`, reduced (issue #3084).
18438    const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
18439
18440    class Target {
18441        int value_;
18442    };
18443
18444    template <typename T>
18445    class Holder {
18446    public:
18447        explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
18448
18449#define HOLDER_DEFINE_OP( id, op )                                             \
18450    template <typename U>                                                      \
18451    constexpr friend auto operator op( Holder&& lhs, U&& rhs )                 \
18452        -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> {       \
18453        return Target{};                                                       \
18454    }
18455
18456        HOLDER_DEFINE_OP( equal, == )
18457#undef HOLDER_DEFINE_OP
18458        T m_lhs;
18459    };
18460
18461    class Tail {};
18462}
18463"#;
18464
18465    #[test]
18466    fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
18467        let source = COLLAPSED_NAMESPACE_HEAD;
18468        let tree = parse_cpp(source);
18469        let target = named_node_at(&tree, source, "class Target");
18470
18471        assert!(
18472            !has_ancestor_kind(target, "namespace_definition"),
18473            "the fixture must reproduce the collapse: the class has no namespace ancestor"
18474        );
18475        let head = target.parent().expect("the collapsed namespace envelope");
18476        assert_eq!(
18477            head.kind(),
18478            "ERROR",
18479            "the fixture must keep the namespace head in an ERROR node"
18480        );
18481
18482        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18483        assert_eq!(
18484            index.enclosing_namespace_components(target, source),
18485            ["app"]
18486        );
18487        assert_eq!(
18488            index.enclosing_namespace_levels(target, source),
18489            vec![(
18490                "app".to_string(),
18491                Range {
18492                    start_byte: 0,
18493                    end_byte: source.len() - 1,
18494                    start_line: 1,
18495                    end_line: 25,
18496                },
18497            )],
18498            "the recovered namespace owns its full construct, not its first member or just its header"
18499        );
18500    }
18501
18502    /// A macro that opens a block: `APP_IF_ACTIVE` expands to `if (active) {`,
18503    /// so the `{` of a scope the source really opens is part of the macro's
18504    /// `preproc_arg` and never becomes a `{` token. The tree therefore holds
18505    /// one more real `}` than real `{`, and a file-global stack has to assign
18506    /// those closes to the wrong opens: the `}` on line 9 takes `check`'s body,
18507    /// `check`'s own `}` on line 11 takes `detail`, line 15 takes `app`, and
18508    /// line 16 arrives with nothing open. This is the state simdjson's
18509    /// `compile_time_json-inl.h` reaches eight times over (#3328).
18510    const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
18511
18512namespace app {
18513namespace detail {
18514
18515bool check(bool active) {
18516  APP_IF_ACTIVE
18517    return true;
18518  }
18519  return false;
18520}
18521
18522class Tail {};
18523
18524} // namespace detail
18525} // namespace app
18526"#;
18527
18528    /// simdjson's `include/simdjson/compile_time_json-inl.h` at `9b33047a`,
18529    /// reduced. The file's C++26 static reflection leaves the whole translation
18530    /// unit an `ERROR`, and inside it tree-sitter re-lexes `case '<punct>': {`:
18531    /// `case` becomes a type, the char literal's own punctuation opens a
18532    /// `compound_literal_expression`, and the literal's closing quote starts a
18533    /// character run that never terminates and consumes the statement's real
18534    /// `{`. The reduction keeps that mis-lex, so the tree loses a
18535    /// scope-opening token here as well and the three namespace opens end up
18536    /// paired with the closes one level in (#3328).
18537    const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
18538namespace compile_time {
18539namespace number_parsing {
18540consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
18541    auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
18542      auto digit = [](uint8_t c) -> uint32_t {
18543      }
18544    }
18545    if (a) {
18546      while (a) {
18547      }
18548    }
18549    switch (c) {
18550    case '{': {
18551    }
18552    }
18553  }
18554  while (a) {
18555    cursor += field.second;
18556    if (a) {
18557    case '{': {
18558      if (a) {
18559      }
18560    }
18561    case 'n': {
18562      if (a) {
18563      }
18564    }
18565    }
18566  }
18567  if (a) {
18568    simdjson_consteval_error("Expected '}'");
18569  }
18570}
18571}
18572}
18573"#;
18574
18575    /// Real `{` and `}` tokens of a tree, the stream the index pairs.
18576    fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
18577        let mut stack = vec![root];
18578        let mut counts = (0, 0);
18579        while let Some(node) = stack.pop() {
18580            match (node.kind(), node.is_missing()) {
18581                ("{", false) => counts.0 += 1,
18582                ("}", false) => counts.1 += 1,
18583                _ => {}
18584            }
18585            let mut cursor = node.walk();
18586            stack.extend(node.children(&mut cursor));
18587        }
18588        counts
18589    }
18590
18591    /// The open brace of `head`, which is the key its close lands under.
18592    fn namespace_open_byte(source: &str, head: &str) -> usize {
18593        let at = source.find(head).expect("fixture namespace head");
18594        at + head.len() - 1
18595    }
18596
18597    #[test]
18598    fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
18599        let source = MACRO_OPENED_SCOPE;
18600        let tree = parse_cpp(source);
18601        let root = tree.root_node();
18602        assert_eq!(
18603            real_brace_token_counts(root),
18604            (4, 5),
18605            "the fixture must reproduce the loss: one scope the source opens \
18606             has no `{{` token, so the stream holds an extra real close"
18607        );
18608
18609        let index = OrphanedNamespaceScopeIndex::build(root, source);
18610        for head in ["namespace app {", "namespace detail {"] {
18611            assert_eq!(
18612                index.matching_close_brace(namespace_open_byte(source, head)),
18613                None,
18614                "`{head}` must not claim a close: the stack had to give it one that \
18615                 belongs to an inner scope (line 15 for `app`, line 11 for \
18616                 `detail`) once the macro's `{{` went missing"
18617            );
18618        }
18619    }
18620
18621    #[test]
18622    fn orphaned_namespace_scope_index_pairs_nothing_on_the_reflection_recovery() {
18623        let source = REFLECTION_CASE_LABEL_RECOVERY;
18624        let tree = parse_cpp(source);
18625        let root = tree.root_node();
18626        let mut stack = vec![root];
18627        let mut mis_lexed_opens = Vec::new();
18628        while let Some(node) = stack.pop() {
18629            if node.kind() == "character"
18630                && &source[node.start_byte()..node.end_byte()] == "{"
18631                && node
18632                    .parent()
18633                    .is_some_and(|parent| parent.kind() != "char_literal")
18634            {
18635                mis_lexed_opens.push(node.start_position().row + 1);
18636            }
18637            let mut cursor = node.walk();
18638            stack.extend(node.children(&mut cursor));
18639        }
18640        assert!(
18641            !mis_lexed_opens.is_empty(),
18642            "the fixture must reproduce the mis-lex: a scope-opening `{{` \
18643             consumed as a `character` of a char literal recovery never closed"
18644        );
18645        assert_eq!(
18646            real_brace_token_counts(root),
18647            (17, 18),
18648            "which is what leaves the brace token stream short one open: \
18649             mis-lexed at {mis_lexed_opens:?}"
18650        );
18651
18652        let index = OrphanedNamespaceScopeIndex::build(root, source);
18653        for head in [
18654            "namespace simdjson {",
18655            "namespace compile_time {",
18656            "namespace number_parsing {",
18657        ] {
18658            assert_eq!(
18659                index.matching_close_brace(namespace_open_byte(source, head)),
18660                None,
18661                "`{head}` must not claim a close: every close after the lost open \
18662                 belongs one level in, so the stack paired the three heads on \
18663                 lines 1..3 with the closes on lines 33..35 instead of 34..36"
18664            );
18665        }
18666    }
18667
18668    #[test]
18669    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
18670        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
18671        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
18672        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
18673        assert!(indexed_namespace_path_is_recoverable(
18674            &["cache".to_string()],
18675            &indexed,
18676            1,
18677        ));
18678    }
18679
18680    #[test]
18681    fn sort_lookup_units_totally_orders_every_identity_field() {
18682        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
18683        let base = CodeUnit::with_signature(
18684            file.clone(),
18685            CodeUnitType::Function,
18686            "scope",
18687            "value",
18688            Some("()".to_string()),
18689            false,
18690        );
18691        let different_kind = CodeUnit::with_signature(
18692            file.clone(),
18693            CodeUnitType::Field,
18694            "scope",
18695            "value",
18696            Some("()".to_string()),
18697            false,
18698        );
18699        let synthetic = base.with_synthetic(true);
18700
18701        let interner = segment_interner();
18702        let mut member_fq = FqName::new();
18703        member_fq.push(interner.intern("scope", SegmentKind::Package));
18704        member_fq.push(interner.intern("value", SegmentKind::Member));
18705        let different_package_boundary = CodeUnit::from_fq(
18706            file.clone(),
18707            CodeUnitType::Function,
18708            member_fq,
18709            0,
18710            Some("()".to_string()),
18711            false,
18712        );
18713
18714        let mut unknown_fq = FqName::new();
18715        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
18716        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
18717        let different_segment_kind = CodeUnit::from_fq(
18718            file,
18719            CodeUnitType::Function,
18720            unknown_fq,
18721            1,
18722            Some("()".to_string()),
18723            false,
18724        );
18725
18726        let input = vec![
18727            base,
18728            different_kind,
18729            synthetic,
18730            different_package_boundary,
18731            different_segment_kind,
18732        ];
18733        let mut expected = input.clone();
18734        sort_lookup_units(&mut expected);
18735        assert!(expected.windows(2).all(|pair| {
18736            let mut ordered = pair.to_vec();
18737            sort_lookup_units(&mut ordered);
18738            ordered == pair && pair[0] != pair[1]
18739        }));
18740
18741        let mut reversed = input.clone();
18742        reversed.reverse();
18743        sort_lookup_units(&mut reversed);
18744        assert_eq!(reversed, expected);
18745
18746        let mut rotated = input;
18747        rotated.rotate_left(2);
18748        sort_lookup_units(&mut rotated);
18749        assert_eq!(rotated, expected);
18750    }
18751
18752    #[test]
18753    fn displaced_preprocessor_terminator_bounds_the_real_guard() {
18754        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";
18755        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
18756        let parse = |source: &str| {
18757            let mut parser = Parser::new();
18758            parser
18759                .set_language(&tree_sitter_cpp::LANGUAGE.into())
18760                .expect("C++ grammar");
18761            parser.parse(source, None).expect("fixture tree")
18762        };
18763
18764        let tree = parse(damaged);
18765        let root = tree.root_node();
18766        let target = damaged.find("target").expect("target byte");
18767        let declaration = root
18768            .descendant_for_byte_range(target, target + "target".len())
18769            .and_then(|mut node| {
18770                loop {
18771                    if node.kind() == "declaration" {
18772                        break Some(node);
18773                    }
18774                    node = node.parent()?;
18775                }
18776            })
18777            .expect("declaration after the displaced terminator");
18778        let conditional = declaration
18779            .parent()
18780            .filter(|node| node.kind() == "preproc_ifdef")
18781            .expect("damaged inner conditional");
18782        let outer = conditional
18783            .parent()
18784            .filter(|node| node.kind() == "preproc_ifdef")
18785            .expect("ordinary outer include guard");
18786        let terminator = cpp_displaced_preprocessor_terminator(conditional)
18787            .expect("structured displaced #endif");
18788        assert_eq!(node_text(terminator, damaged), "#endif");
18789        assert!(terminator.end_byte() <= declaration.start_byte());
18790        assert!(!preprocessor_conditional_contains_descendant(
18791            conditional,
18792            declaration
18793        ));
18794        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
18795        assert!(preprocessor_conditional_contains_descendant(
18796            outer,
18797            declaration
18798        ));
18799
18800        let tree = parse(guarded);
18801        let conditional = tree
18802            .root_node()
18803            .named_child(0)
18804            .filter(|node| node.kind() == "preproc_ifdef")
18805            .expect("ordinary conditional");
18806        let declaration = conditional
18807            .named_children(&mut conditional.walk())
18808            .find(|node| node.kind() == "declaration")
18809            .expect("guarded declaration");
18810        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18811        assert!(preprocessor_conditional_contains_descendant(
18812            conditional,
18813            declaration
18814        ));
18815
18816        let damaged_alternative = format!(
18817            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
18818            "UNUSED(value)\n".repeat(64)
18819        );
18820        let tree = parse(&damaged_alternative);
18821        let conditional = tree
18822            .root_node()
18823            .named_child(0)
18824            .filter(|node| node.kind() == "preproc_ifdef")
18825            .expect("outer conditional with an alternative");
18826        assert!(conditional.has_error());
18827        assert!(conditional.child_by_field_name("alternative").is_some());
18828        assert!(
18829            conditional
18830                .child(conditional.child_count() - 1)
18831                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
18832        );
18833        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18834
18835        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";
18836        let tree = parse(split_declaration);
18837        let root = tree.root_node();
18838        let conditional = root
18839            .named_children(&mut root.walk())
18840            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
18841            .expect("split declaration conditional");
18842        let target = split_declaration
18843            .find("static int target")
18844            .expect("target byte");
18845        let boundary =
18846            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
18847        assert!(boundary.end_byte <= target, "{boundary:?}");
18848        assert_eq!(boundary.end_line, 9, "{boundary:?}");
18849        let target_node = root
18850            .descendant_for_byte_range(target, target + "static".len())
18851            .expect("target node");
18852        assert!(!preprocessor_conditional_contains_descendant(
18853            conditional,
18854            target_node
18855        ));
18856    }
18857
18858    #[test]
18859    fn fragmented_reference_guard_is_recovered() {
18860        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";
18861        let mut parser = Parser::new();
18862        parser
18863            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18864            .expect("C++ grammar");
18865        let tree = parser.parse(source, None).expect("fixture tree");
18866        let start = source.rfind("helper").expect("reference byte");
18867        let node = tree
18868            .root_node()
18869            .descendant_for_byte_range(start, start + "helper".len())
18870            .expect("reference node");
18871        let mut expected = HashSet::default();
18872        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
18873            vec![
18874                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
18875                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
18876            ],
18877        )));
18878        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
18879    }
18880
18881    #[test]
18882    fn expression_defined_and_ifndef_guards_are_incompatible() {
18883        let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
18884        let mut parser = Parser::new();
18885        parser
18886            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18887            .expect("C++ grammar");
18888        let tree = parser.parse(source, None).expect("fixture tree");
18889        let root = tree.root_node();
18890        let selected_start = source.find("selected").expect("selected declaration");
18891        let rejected_start = source.find("rejected").expect("rejected declaration");
18892        let selected = root
18893            .descendant_for_byte_range(selected_start, selected_start + "selected".len())
18894            .expect("selected node");
18895        let rejected = root
18896            .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
18897            .expect("rejected node");
18898        let selected_guards =
18899            preprocessor_guard_environment(selected, source).expect("selected guards");
18900        let rejected_guards =
18901            preprocessor_guard_environment(rejected, source).expect("rejected guards");
18902
18903        assert!(
18904            merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
18905            "opposite spellings of one macro guard must contradict"
18906        );
18907    }
18908
18909    #[test]
18910    fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
18911        let source = r#"#ifdef _WIN32
18912#if defined(__cplusplus)
18913extern "C"
18914#endif
18915int platform_api(void);
18916#endif
18917
18918#ifdef _WIN32
18919static int entropy_target(void) { return 0; }
18920#else
18921#ifdef HAVE_COMMON_RANDOM
18922static int other_target(void) { return 0; }
18923#elif defined(HAVE_GETENTROPY)
18924static int entropy_target(void) { return 1; }
18925static int use_entropy(void) { return entropy_target(); }
18926#endif
18927#endif
18928"#;
18929        let mut parser = Parser::new();
18930        parser
18931            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18932            .expect("C++ grammar");
18933        let tree = parser.parse(source, None).expect("fixture tree");
18934        let start = source.rfind("entropy_target()").expect("reference");
18935        let node = tree
18936            .root_node()
18937            .descendant_for_byte_range(start, start + "entropy_target".len())
18938            .expect("reference node");
18939        let guards = preprocessor_guard_environment(node, source).expect("active C branch");
18940        assert!(
18941            guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
18942            "{guards:#?}"
18943        );
18944        assert!(
18945            guards.contains(&PreprocessorGuard::Undefined(
18946                "HAVE_COMMON_RANDOM".to_string()
18947            )),
18948            "{guards:#?}"
18949        );
18950        assert!(
18951            guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
18952            "{guards:#?}"
18953        );
18954        assert!(
18955            !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
18956            "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
18957        );
18958    }
18959
18960    #[test]
18961    fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
18962        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";
18963        let mut parser = Parser::new();
18964        parser
18965            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18966            .expect("C++ grammar");
18967        let tree = parser.parse(source, None).expect("fixture tree");
18968        let root = tree.root_node();
18969        let node_at = |text: &str, start: usize| {
18970            root.descendant_for_byte_range(start, start + text.len())
18971                .expect("token node")
18972        };
18973
18974        let key_start = source.find("case KEY").expect("case label") + "case ".len();
18975        let guard_start = source.find("ENABLE_KEYS").expect("guard name");
18976        assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
18977        assert!(!is_ordinary_macro_reference_node(node_at(
18978            "ENABLE_KEYS",
18979            guard_start,
18980        )));
18981    }
18982
18983    #[test]
18984    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
18985        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";
18986        let mut parser = Parser::new();
18987        parser
18988            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18989            .expect("C++ grammar");
18990        let tree = parser.parse(source, None).expect("fixture tree");
18991        let root = tree.root_node();
18992        let definition_start = source.find("target(void)").expect("definition");
18993        let reference_start = source.rfind("target()").expect("reference");
18994        let definition = root
18995            .descendant_for_byte_range(definition_start, definition_start + "target".len())
18996            .expect("definition node");
18997        let reference = root
18998            .descendant_for_byte_range(reference_start, reference_start + "target".len())
18999            .expect("reference node");
19000        let required =
19001            preprocessor_guard_environment(definition, source).expect("definition guard");
19002        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
19003        assert!(guard_requirements_hold_at_reference(
19004            &required,
19005            Some(&active)
19006        ));
19007    }
19008
19009    #[test]
19010    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
19011        let source = "g_autoptr(FuChunkArray) self = make_array();";
19012        let mut parser = Parser::new();
19013        parser
19014            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19015            .expect("C++ grammar");
19016        let tree = parser.parse(source, None).expect("fixture tree");
19017        let statement = tree.root_node().named_child(0).expect("statement");
19018        let binding =
19019            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
19020        assert_eq!(binding.name, "self");
19021        assert_eq!(binding.type_name, "FuChunkArray");
19022        assert_eq!(binding.pointer_depth, 1);
19023
19024        let near_miss = "holder(FuChunkArray) self = make_array();";
19025        let tree = parser.parse(near_miss, None).expect("near-miss tree");
19026        let statement = tree.root_node().named_child(0).expect("statement");
19027        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
19028    }
19029
19030    #[test]
19031    fn boolean_guard_normalization_proves_equivalence_and_implication() {
19032        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
19033        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
19034        let negated_windows_branch =
19035            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
19036        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
19037        assert_eq!(negated_windows_branch, portable);
19038
19039        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
19040        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
19041        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
19042        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
19043        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
19044        assert!(fallback_branch.implies(&fallback_declaration));
19045        assert!(
19046            BooleanGuardExpression::Truthy("FEATURE".to_string())
19047                .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
19048        );
19049        assert!(
19050            BooleanGuardExpression::Undefined("FEATURE".to_string())
19051                .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
19052        );
19053        assert!(
19054            !BooleanGuardExpression::Defined("FEATURE".to_string())
19055                .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
19056        );
19057        assert!(!fallback_declaration.implies(&fallback_branch));
19058    }
19059
19060    #[test]
19061    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
19062        let source = "static int helper(const char *left, wchar_t *right) { return 0; }\nint caller(wchar_t *template) {\n    return helper(NULL, template); /* bound */\n}\nint unbound(void) {\n    return helper(NULL, template); /* unbound */\n}\n";
19063        let mut parser = Parser::new();
19064        parser
19065            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19066            .expect("C++ grammar");
19067        let tree = parser.parse(source, None).expect("fixture tree");
19068        let root = tree.root_node();
19069        let call = |marker: &str| {
19070            let start = source.find(marker).expect("call marker");
19071            let mut node = root
19072                .descendant_for_byte_range(start, start + "helper".len())
19073                .expect("call name node");
19074            loop {
19075                if node.kind() == "call_expression" {
19076                    break node;
19077                }
19078                node = node.parent().expect("call expression ancestor");
19079            }
19080        };
19081        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
19082        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
19083        let keyword_call = call("helper(NULL, template); /* bound */");
19084        let keyword_arguments = keyword_call
19085            .child_by_field_name("arguments")
19086            .expect("keyword argument list");
19087        assert_eq!(
19088            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
19089            1
19090        );
19091        assert_eq!(
19092            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
19093            0
19094        );
19095
19096        let unbound_call = call("helper(NULL, template); /* unbound */");
19097        let unbound_arguments = unbound_call
19098            .child_by_field_name("arguments")
19099            .expect("unbound argument list");
19100        assert_eq!(
19101            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
19102            0
19103        );
19104    }
19105
19106    #[test]
19107    fn c_function_declarator_recovery_accepts_invocations_not_binders() {
19108        let source = r#"#define MAKE(type) type *value
19109MAKE(int *);
19110typedef struct Item Item;
19111struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
19112void gen_op_movl(void *s, int first, int second) { }
19113const char *strZ(const char *value) { return value; }
19114int body(void *s) {
19115    MAKE(int *);
19116    gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
19117                offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
19118    execvp(strZ(value), UNCONSTIFY(char **, args));
19119}
19120 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
19121    if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
19122int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
19123    if (d->units) {
19124        switch (target->nodetype) {
19125        case 1:
19126        case 2:
19127            break;
19128        default:
19129            AMEND_WRONG_NODETYPE("deviation", "replace", "units");
19130        }
19131        DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
19132        lysdict_remove(ctx, ((struct Item *)target)->units);
19133        DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
19134    }
19135     return 0;
19136 }
19137STATIC EFI_STATUS Encode () { return 0; }
19138"#;
19139        let tree = parse_cpp(source);
19140        let top_macro_start = source.find("MAKE(int *);").expect("top macro");
19141        let top_macro = tree
19142            .root_node()
19143            .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
19144            .expect("top macro node");
19145        let body_macro_start = source
19146            .match_indices("MAKE(int *);")
19147            .nth(1)
19148            .expect("body macro")
19149            .0;
19150        let body_macro = tree
19151            .root_node()
19152            .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
19153            .expect("body macro node");
19154        let function_call_start = source
19155            .find("gen_op_movl(s, offsetof(CPUX86State")
19156            .expect("function call");
19157        let function_call = tree
19158            .root_node()
19159            .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
19160            .expect("function call node");
19161        let strz_start = source.find("strZ(value)").expect("nested function call");
19162        let strz = tree
19163            .root_node()
19164            .named_descendant_for_byte_range(strz_start, strz_start + 4)
19165            .expect("nested function call node");
19166        let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
19167        let recovered_call = tree
19168            .root_node()
19169            .named_descendant_for_byte_range(
19170                recovered_call_start,
19171                recovered_call_start + "lysdict_remove".len(),
19172            )
19173            .expect("recovered call node");
19174        let binder_start = source.find("Encode").expect("binder");
19175        let binder = tree
19176            .root_node()
19177            .named_descendant_for_byte_range(binder_start, binder_start + 6)
19178            .expect("binder node");
19179
19180        assert!(recovered_c_function_declarator_invocation(top_macro));
19181        assert!(recovered_c_function_declarator_invocation(body_macro));
19182        assert!(recovered_c_function_declarator_invocation(function_call));
19183        assert!(recovered_c_function_declarator_invocation(strz));
19184        assert!(recovered_c_function_declarator_invocation(recovered_call));
19185        assert!(!recovered_c_function_declarator_invocation(binder));
19186    }
19187
19188    #[test]
19189    fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
19190        let source = r#"typedef int krb5_context;
19191int helper(int first, int second) { return first + second; }
19192static krb5_context ctx;
19193int main(int argc, char **argv) {
19194    int ccinitial;
19195    const char *collection_name, *typename;
19196    typename = helper(ctx, ccinitial);
19197    return 0;
19198}
19199"#;
19200        let tree = parse_cpp(source);
19201        let ctx = tree
19202            .root_node()
19203            .descendant_for_byte_range(
19204                source.find("ctx, ccinitial").expect("ctx argument"),
19205                source.find("ctx, ccinitial").expect("ctx argument") + 3,
19206            )
19207            .expect("ctx node");
19208        let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
19209        let ccinitial = tree
19210            .root_node()
19211            .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
19212            .expect("sibling node");
19213        let typename = named_node_at(&tree, source, "typename = helper");
19214        let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
19215
19216        assert_eq!(ctx.kind(), "identifier");
19217        assert!(recovered_c_parenthesized_declarator_reference(ctx));
19218        assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
19219        assert!(!recovered_c_parenthesized_declarator_reference(typename));
19220        assert!(!recovered_c_parenthesized_declarator_reference(helper));
19221    }
19222
19223    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
19224        let mut parser = Parser::new();
19225        parser
19226            .set_language(&tree_sitter_cpp::LANGUAGE.into())
19227            .expect("C++ grammar");
19228        let tree = parser.parse(source, None).expect("C++ fixture tree");
19229        let mut stack = vec![tree.root_node()];
19230        while let Some(node) = stack.pop() {
19231            if node.kind() == "enum_specifier" {
19232                return flattened_macro_namespace_components(node, source);
19233            }
19234            let mut cursor = node.walk();
19235            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
19236            stack.extend(children.into_iter().rev());
19237        }
19238        None
19239    }
19240
19241    #[test]
19242    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
19243        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19244namespace detail
19245{
19246enum class value_t { null };
19247}
19248NLOHMANN_JSON_NAMESPACE_END
19249NLOHMANN_JSON_NAMESPACE_BEGIN
19250namespace next
19251{
19252struct next_type {};
19253}
19254NLOHMANN_JSON_NAMESPACE_END
19255"#;
19256        assert_eq!(
19257            first_enum_flattened_namespace(complete),
19258            Some(vec!["detail".to_string()])
19259        );
19260
19261        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
19262        assert_eq!(
19263            first_enum_flattened_namespace(&stale_end),
19264            Some(vec!["detail".to_string()]),
19265            "a stale end marker before the begin marker must not replace the intended namespace"
19266        );
19267
19268        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19269namespace detail
19270{
19271enum class value_t { null };
19272}
19273struct next_type {};
19274"#;
19275        assert_eq!(first_enum_flattened_namespace(incomplete), None);
19276    }
19277}
19278
19279/// Comparator laws for the total C++ lookup order introduced by #1876.
19280///
19281/// `sort_lookup_units` is the single tie-break the C++ resolver applies before
19282/// any "first wins" selection (template families in #1836, the visible
19283/// identifier index, the type-candidate lists). If its comparator is not a
19284/// total order over CodeUnit identity, some pair stays tied and the survivor
19285/// falls back to the order the units arrived in -- which is FxHash iteration
19286/// order over keys whose hash covers the absolute workspace root. That is the
19287/// exact mechanism behind #1836 and the #414 / #432 heisenbug, so the laws are
19288/// checked generatively rather than on one hand-picked list.
19289///
19290/// CodeUnit identity is `source`, `kind`, `fq`, `package_segment_count`,
19291/// `signature` and `synthetic` (see `impl PartialEq for CodeUnit`); a CodeUnit
19292/// carries no range, so declaration ranges are covered by the workspace-level
19293/// property in `tests/suite_analyzers/determinism_properties.rs` instead.
19294#[cfg(test)]
19295mod lookup_order_properties {
19296    use super::*;
19297    use proptest::prelude::*;
19298
19299    /// Segment spellings the C++ extractor and the shared renderer actually
19300    /// produce, including the `$`-joined nested spellings and non-ASCII
19301    /// identifiers that a byte-wise comparison has to keep apart.
19302    const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
19303    const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
19304    /// Two roots so the order is pinned across workspaces as well as inside
19305    /// one: the root path is precisely the byte string that used to leak into
19306    /// iteration order.
19307    const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
19308    const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
19309    const KINDS: [CodeUnitType; 6] = [
19310        CodeUnitType::Class,
19311        CodeUnitType::Function,
19312        CodeUnitType::Field,
19313        CodeUnitType::Module,
19314        CodeUnitType::Macro,
19315        CodeUnitType::FileScope,
19316    ];
19317
19318    /// Where one unit sits relative to another under the comparator that
19319    /// `sort_lookup_units` owns.
19320    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
19321    enum ProbedOrder {
19322        Before,
19323        Tied,
19324        After,
19325        /// Both directions reported "strictly first": the comparator is not
19326        /// dual, and no sort over it can be order-independent.
19327        Contradictory,
19328    }
19329
19330    impl ProbedOrder {
19331        fn mirror(self) -> Self {
19332            match self {
19333                ProbedOrder::Before => ProbedOrder::After,
19334                ProbedOrder::After => ProbedOrder::Before,
19335                other => other,
19336            }
19337        }
19338
19339        /// -1 / 0 / +1, so transitivity reads as the `<= 0` law.
19340        fn signum(self) -> i8 {
19341            match self {
19342                ProbedOrder::Before => -1,
19343                ProbedOrder::Tied => 0,
19344                ProbedOrder::After => 1,
19345                ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
19346            }
19347        }
19348    }
19349
19350    /// Read the comparator through its only caller.
19351    ///
19352    /// `sort_lookup_units` is a stable sort, so for a two-element slice the
19353    /// output says exactly whether the comparator put the second element
19354    /// strictly first. Sorting both arrangements of one pair therefore reports
19355    /// the comparator's verdict in both directions, including the contradictory
19356    /// case a single sort would hide.
19357    fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
19358        if left == right {
19359            // A stable sort cannot distinguish two equal values, and `Equal` is
19360            // the only verdict a total order can give them.
19361            return ProbedOrder::Tied;
19362        }
19363        let mut forward = vec![left.clone(), right.clone()];
19364        sort_lookup_units(&mut forward);
19365        let mut backward = vec![right.clone(), left.clone()];
19366        sort_lookup_units(&mut backward);
19367        let left_first = backward[0] == *left;
19368        let right_first = forward[0] == *right;
19369        match (left_first, right_first) {
19370            (true, true) => ProbedOrder::Contradictory,
19371            (true, false) => ProbedOrder::Before,
19372            (false, true) => ProbedOrder::After,
19373            (false, false) => ProbedOrder::Tied,
19374        }
19375    }
19376
19377    /// `(kind, text)` per segment. `CodeUnit`'s own `Debug` prints interned
19378    /// segment IDs, which are process-local and say nothing about a failure.
19379    fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
19380        let interner = segment_interner();
19381        unit.fq()
19382            .segments()
19383            .iter()
19384            .map(|&id| {
19385                let (text, kind) = interner.resolve(id);
19386                (kind.name(), text)
19387            })
19388            .collect()
19389    }
19390
19391    fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
19392        (
19393            0..ROOT_NAMES.len(),
19394            0..REL_PATHS.len(),
19395            0..KINDS.len(),
19396            prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
19397            0..3usize,
19398            0..SIGNATURES.len(),
19399            any::<bool>(),
19400        )
19401            .prop_map(
19402                |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
19403                    let source = ProjectFile::new(
19404                        std::env::temp_dir().join(ROOT_NAMES[root]),
19405                        REL_PATHS[rel_path],
19406                    );
19407                    let interner = segment_interner();
19408                    let mut fq = FqName::new();
19409                    for (atom, segment_kind) in &segments {
19410                        fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
19411                    }
19412                    // `from_fq` requires a non-empty declaration tail.
19413                    let package_segment_count = package_prefix % fq.len();
19414                    CodeUnit::from_fq(
19415                        source,
19416                        KINDS[kind],
19417                        fq,
19418                        package_segment_count,
19419                        SIGNATURES[signature].map(str::to_string),
19420                        synthetic,
19421                    )
19422                },
19423            )
19424    }
19425
19426    proptest! {
19427        #![proptest_config(ProptestConfig::with_cases(256))]
19428
19429        /// Reflexivity and duality: a unit ties with itself, and no pair is
19430        /// strictly first in both directions.
19431        #[test]
19432        fn lookup_order_is_reflexive_and_dual(
19433            left in code_unit_strategy(),
19434            right in code_unit_strategy(),
19435        ) {
19436            prop_assert_eq!(
19437                probe_order(&left, &left),
19438                ProbedOrder::Tied,
19439                "a unit must tie with itself: {:?}",
19440                left
19441            );
19442            let forward = probe_order(&left, &right);
19443            prop_assert_ne!(
19444                forward,
19445                ProbedOrder::Contradictory,
19446                "comparator put each of these strictly first: left={:?} right={:?}",
19447                left,
19448                right
19449            );
19450            prop_assert_eq!(
19451                probe_order(&right, &left),
19452                forward.mirror(),
19453                "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
19454                left,
19455                right
19456            );
19457        }
19458
19459        /// Transitivity: `a <= b` and `b <= c` imply `a <= c`.
19460        #[test]
19461        fn lookup_order_is_transitive(
19462            a in code_unit_strategy(),
19463            b in code_unit_strategy(),
19464            c in code_unit_strategy(),
19465        ) {
19466            let ab = probe_order(&a, &b);
19467            let bc = probe_order(&b, &c);
19468            let ac = probe_order(&a, &c);
19469            for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
19470                prop_assert_ne!(
19471                    probed,
19472                    ProbedOrder::Contradictory,
19473                    "comparator is not dual over {}: a={:?} b={:?} c={:?}",
19474                    pair,
19475                    a,
19476                    b,
19477                    c
19478                );
19479            }
19480            if ab.signum() <= 0 && bc.signum() <= 0 {
19481                prop_assert!(
19482                    ac.signum() <= 0,
19483                    "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
19484                     a={:?} b={:?} c={:?}",
19485                    ab,
19486                    bc,
19487                    ac,
19488                    a,
19489                    b,
19490                    c
19491                );
19492            }
19493        }
19494
19495        /// The property #1876 exists for: only identical identities may tie.
19496        /// A tie between distinct units is the residual hash-order dependence.
19497        #[test]
19498        fn lookup_order_separates_distinct_identities(
19499            left in code_unit_strategy(),
19500            right in code_unit_strategy(),
19501        ) {
19502            if probe_order(&left, &right) == ProbedOrder::Tied {
19503                prop_assert_eq!(
19504                    &left,
19505                    &right,
19506                    "distinct identities tied, so their order is whatever order they \
19507                     arrived in: left_segments={:?} right_segments={:?}",
19508                    fq_segments(&left),
19509                    fq_segments(&right)
19510                );
19511            }
19512        }
19513
19514        /// The consequence the resolver relies on: the sorted list is a
19515        /// function of the SET of units, not of the order they were pushed in.
19516        #[test]
19517        fn lookup_sort_is_permutation_invariant(
19518            units in prop::collection::vec(code_unit_strategy(), 1..=8),
19519        ) {
19520            let mut sorted = units.clone();
19521            sort_lookup_units(&mut sorted);
19522            for rotation in 0..units.len() {
19523                for reversed in [false, true] {
19524                    let mut permuted = units.clone();
19525                    permuted.rotate_left(rotation);
19526                    if reversed {
19527                        permuted.reverse();
19528                    }
19529                    sort_lookup_units(&mut permuted);
19530                    prop_assert_eq!(
19531                        &permuted,
19532                        &sorted,
19533                        "sorting a permutation gave a different list \
19534                         (rotation={}, reversed={}): input={:?}",
19535                        rotation,
19536                        reversed,
19537                        units
19538                    );
19539                }
19540            }
19541        }
19542    }
19543}