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;
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    template_arguments: Option<Vec<CppTemplateExpression>>,
1740}
1741
1742#[derive(Clone, PartialEq, Eq)]
1743enum StructuredAliasTarget {
1744    Builtin,
1745    Named {
1746        components: Vec<String>,
1747        global: bool,
1748        arguments: Option<Vec<CppTemplateExpression>>,
1749    },
1750}
1751
1752struct CppAlias {
1753    name: String,
1754    target: String,
1755    namespace: Option<String>,
1756}
1757
1758type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1759
1760/// Why template-argument resolution failed. Definition diagnostics render
1761/// each mode differently; graph scans only care that the resolution is
1762/// unproven and match `Err(_)`.
1763#[derive(Debug, Clone, PartialEq, Eq)]
1764pub enum CppTemplateResolutionError {
1765    /// A template alias expansion revisited `alias`.
1766    AliasCycle { alias: CodeUnit },
1767    /// The explicit arguments do not bind to the declared template parameters.
1768    ArgumentBinding,
1769    /// Bound arguments do not substitute into the alias target's arguments.
1770    Substitution,
1771    /// No visible primary template declaration could be selected and
1772    /// reconciled for the specialization family.
1773    PrimarySelection,
1774    /// More than one applicable specialization remains and none is strictly
1775    /// more specialized than every other candidate.
1776    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1777}
1778
1779/// The ambiguity candidates, deduplicated to one representative per visible
1780/// symbol so a diagnostic lists each contender once.
1781fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1782    let mut distinct: Vec<CodeUnit> = Vec::new();
1783    for unit in units {
1784        if !distinct
1785            .iter()
1786            .any(|existing| same_visible_symbol(existing, unit))
1787        {
1788            distinct.push(unit.clone());
1789        }
1790    }
1791    distinct
1792}
1793
1794/// Enumerate macro lexical references while constructing visibility only if
1795/// the bounded source walk finds a definition or identifier call candidate.
1796pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1797    visibility: Factory,
1798    file: &ProjectFile,
1799    root: Node<'_>,
1800    source: &str,
1801    max_references: usize,
1802    cancelled: impl FnMut() -> bool,
1803) -> MacroLexicalReferences
1804where
1805    Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1806{
1807    crate::graph::macro_lexical::all_references(
1808        visibility,
1809        file,
1810        root,
1811        source,
1812        max_references,
1813        cancelled,
1814    )
1815}
1816
1817impl<'a> VisibilityIndex<'a> {
1818    pub fn cpp(&self) -> &'a dyn CppSource {
1819        self.cpp
1820    }
1821
1822    /// The request-scope proof this index was built with (issue #2414 step 3).
1823    pub fn token(&self) -> QueryToken<'a> {
1824        self.token
1825    }
1826
1827    /// Whether `file` has a structured unresolved include that is active before
1828    /// the reference at `before_byte`.
1829    ///
1830    /// Include facts are collected from the prepared tree once per visibility
1831    /// query. The reference position is still evaluated for each call because
1832    /// preprocessor visibility depends on the reference's own conditional
1833    /// context.
1834    pub fn has_unresolved_include_visible_before(
1835        &self,
1836        file: &ProjectFile,
1837        before_byte: usize,
1838    ) -> bool {
1839        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
1840            return false;
1841        };
1842        let cell = self
1843            .structured_include_fact_cells
1844            .lock()
1845            .expect("C++ structured include-fact cache poisoned")
1846            .entry(file.clone())
1847            .or_default()
1848            .clone();
1849        let facts = cell.get_or_init(|| collect_structured_include_facts(prepared.as_ref()));
1850        has_unresolved_include_visible_before_in_prepared(
1851            file,
1852            prepared.as_ref(),
1853            self.cpp.include_target_index(),
1854            facts,
1855            before_byte,
1856        )
1857    }
1858
1859    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1860    /// bypassing the include-closure walk [`Self::build`] performs.
1861    ///
1862    /// The resolver's own unit tests drive the type-resolution paths against a
1863    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1864    /// because they need a real `CppAnalyzer`, so the struct literal they used
1865    /// to write inline is here instead of thirty-three public fields.
1866    #[cfg(any(test, feature = "test-support"))]
1867    pub fn from_visible_files_for_test(
1868        cpp: &'a dyn CppSource,
1869        token: QueryToken<'a>,
1870        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1871    ) -> Self {
1872        let visible_source_files_by_root = visible_by_file
1873            .iter()
1874            .map(|(file, visible)| {
1875                (
1876                    file.clone(),
1877                    visible
1878                        .iter()
1879                        .map(|unit| unit.source().clone())
1880                        .chain(std::iter::once(file.clone()))
1881                        .collect(),
1882                )
1883            })
1884            .collect();
1885        let mut global_field_internal_linkage = HashMap::default();
1886        Self {
1887            cpp,
1888            token,
1889            visible_by_identifier: build_visible_identifier_index(
1890                &CppGraphSource::from_source(cpp, token),
1891                &visible_by_file,
1892                &visible_source_files_by_root,
1893                &mut global_field_internal_linkage,
1894            ),
1895            global_field_internal_linkage,
1896            visible_by_file,
1897            visible_source_files_by_root,
1898            alias_cells: Mutex::new(HashMap::default()),
1899            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1900            parser_alias_target_matches: RwLock::new(HashMap::default()),
1901            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1902            project_using_index: OnceLock::new(),
1903            callable_reference_specs: Mutex::new(HashMap::default()),
1904            structured_include_fact_cells: Mutex::new(HashMap::default()),
1905            include_activation_cells: Mutex::new(HashMap::default()),
1906            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1907            include_path_admission_cells: Mutex::new(HashMap::default()),
1908            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1909            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1910            conditional_include_projection_state_count: AtomicUsize::new(0),
1911            conditional_include_target_state_count: AtomicUsize::new(0),
1912            include_activation_build_count: AtomicUsize::new(0),
1913            using_donor_activation_count: AtomicUsize::new(0),
1914            using_namespace_lookup_count: AtomicUsize::new(0),
1915            using_name_candidate_inspection_count: AtomicUsize::new(0),
1916            callable_reference_spec_build_count: AtomicUsize::new(0),
1917            alias_source_parse_counts: Mutex::new(HashMap::default()),
1918            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1919            parser_alias_fallback_calls: AtomicUsize::new(0),
1920            parser_alias_fallback_files: AtomicUsize::new(0),
1921            parser_alias_source_parses: AtomicUsize::new(0),
1922            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1923            field_type_facts: Mutex::new(HashMap::default()),
1924            structured_alias_targets: Mutex::new(HashMap::default()),
1925            callable_comparables: Mutex::new(HashMap::default()),
1926            comparable_name_declarations: Mutex::new(HashMap::default()),
1927            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1928            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1929            precise_parent_cache: Mutex::new(HashMap::default()),
1930            c_tag_kind_cache: Mutex::new(HashMap::default()),
1931            c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
1932            macro_event_cells: Mutex::new(HashMap::default()),
1933            macro_event_name_sets: Mutex::new(HashMap::default()),
1934            macro_include_protection_cells: Mutex::new(HashMap::default()),
1935            macro_environment_checkpoints: Mutex::new(HashMap::default()),
1936            macro_replacements: Mutex::new(HashMap::default()),
1937            macro_local_binding_templates: Mutex::new(HashMap::default()),
1938            macro_lexical_templates: Mutex::new(HashMap::default()),
1939            macro_replacement_bodies: Mutex::new(HashMap::default()),
1940            macro_type_parameters: Mutex::new(HashMap::default()),
1941            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1942            macro_replacement_parse_count: AtomicUsize::new(0),
1943            macro_event_application_count: AtomicUsize::new(0),
1944            macro_environment_checkpoint_build_count: AtomicUsize::new(0),
1945            macro_environment_copy_count: AtomicUsize::new(0),
1946            macro_environment_request_count: AtomicUsize::new(0),
1947            cpp_template_metadata: HashMap::default(),
1948            cpp_template_families: HashMap::default(),
1949            qualified_candidate_inspections: AtomicUsize::new(0),
1950            target_preserving_type_resolution_count: AtomicUsize::new(0),
1951            visibility_identifier_lookup_count: 0,
1952            visibility_identifier_batch_count: 0,
1953        }
1954    }
1955
1956    /// The index's own C++ source, in the dispatching-analyzer shape.
1957    ///
1958    /// Four resolution paths reach the workspace through the C++ analyzer they
1959    /// already hold rather than through the analyzer the query was issued
1960    /// against; before the move they passed `&CppAnalyzer` straight into a
1961    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
1962    fn cpp_source(&self) -> CppGraphSource<'a> {
1963        CppGraphSource::from_source(self.cpp, self.token)
1964    }
1965
1966    pub fn build(
1967        cpp: &'a dyn CppSource,
1968        token: QueryToken<'a>,
1969        analyzer: &CppGraphSource<'_>,
1970        roots: &HashSet<ProjectFile>,
1971    ) -> Self {
1972        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1973    }
1974
1975    pub fn build_with_cancellation(
1976        cpp: &'a dyn CppSource,
1977        token: QueryToken<'a>,
1978        analyzer: &CppGraphSource<'_>,
1979        roots: &HashSet<ProjectFile>,
1980        cancellation: Option<&CancellationToken>,
1981    ) -> Self {
1982        let visibility_started = Instant::now();
1983        let include_targets = cpp.include_target_index();
1984        let includes_started = Instant::now();
1985        let mut include_graph = IncludeGraph::default();
1986        for root in roots {
1987            include_graph.extend_with(root, cancellation, &mut |file| {
1988                cpp_include_paths(&cpp.visibility_import_statements(token, file))
1989                    .into_iter()
1990                    .flat_map(|include| {
1991                        resolve_include_targets_with_index(file, &include, include_targets)
1992                    })
1993                    .collect()
1994            });
1995        }
1996        let include_elapsed = includes_started.elapsed();
1997        let include_file_count = include_graph.files().count();
1998        let visible_source_files_by_root = roots
1999            .iter()
2000            .map(|root| {
2001                (
2002                    root.clone(),
2003                    include_graph.reachable_files(root, cancellation),
2004                )
2005            })
2006            .collect::<HashMap<_, _>>();
2007        let mut visibility_stats = BoundedVisibilityStats::default();
2008        let mut visible_by_file = build_bounded_visible_declarations(
2009            cpp,
2010            token,
2011            analyzer,
2012            roots,
2013            &visible_source_files_by_root,
2014            cancellation,
2015            &mut visibility_stats,
2016        );
2017        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2018            eprintln!(
2019                "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={}",
2020                visibility_started.elapsed().as_millis(),
2021                include_elapsed.as_millis(),
2022                include_file_count,
2023                visibility_stats.rounds,
2024                visibility_stats.root_names,
2025                visibility_stats.identifier_lookups,
2026                visibility_stats.identifier_batches,
2027                visibility_stats.candidate_units,
2028                visibility_stats.candidate_sources,
2029                visibility_stats.declaration_reads,
2030                visibility_stats.declaration_units,
2031                visibility_stats.selected_units,
2032                visibility_stats.dependency_ast_nodes,
2033                visibility_stats.dependency_names,
2034                visibility_stats.lookup_elapsed.as_millis(),
2035                visibility_stats.declaration_elapsed.as_millis(),
2036                visibility_stats.dependency_ast_elapsed.as_millis(),
2037            );
2038        }
2039        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2040        let finalize_started = Instant::now();
2041        if report_stats {
2042            eprintln!(
2043                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2044                visible_by_file.len(),
2045                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2046            );
2047        }
2048        let owner_started = Instant::now();
2049        if report_stats {
2050            eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2051        }
2052        let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2053        if report_stats {
2054            eprintln!(
2055                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2056                owner_stats.unseen_owners,
2057                owner_stats.definition_lookups,
2058                owner_stats.admitted,
2059                owner_started.elapsed().as_millis(),
2060            );
2061        }
2062        let mut global_field_internal_linkage = HashMap::default();
2063        let identifier_started = Instant::now();
2064        if report_stats {
2065            eprintln!(
2066                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2067            );
2068        }
2069        let visible_by_identifier = build_visible_identifier_index(
2070            analyzer,
2071            &visible_by_file,
2072            &visible_source_files_by_root,
2073            &mut global_field_internal_linkage,
2074        );
2075        if report_stats {
2076            eprintln!(
2077                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2078                visible_by_identifier.len(),
2079                visible_by_identifier
2080                    .values()
2081                    .map(HashMap::len)
2082                    .sum::<usize>(),
2083                visible_by_identifier
2084                    .values()
2085                    .flat_map(HashMap::values)
2086                    .map(Vec::len)
2087                    .sum::<usize>(),
2088                identifier_started.elapsed().as_millis(),
2089            );
2090        }
2091        let mut cpp_template_metadata = HashMap::default();
2092        let metadata_started = Instant::now();
2093        let mut template_classes = 0usize;
2094        if report_stats {
2095            eprintln!(
2096                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2097            );
2098        }
2099        for unit in visible_by_file
2100            .values()
2101            .flatten()
2102            .filter(|unit| unit.is_class())
2103        {
2104            template_classes += 1;
2105            if cpp_template_metadata.contains_key(unit) {
2106                continue;
2107            }
2108            if let Some(metadata) = cpp.template_metadata(unit) {
2109                cpp_template_metadata.insert(unit.clone(), metadata);
2110            }
2111        }
2112        if report_stats {
2113            eprintln!(
2114                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2115                template_classes,
2116                cpp_template_metadata.len(),
2117                metadata_started.elapsed().as_millis(),
2118            );
2119        }
2120        let families_started = Instant::now();
2121        if report_stats {
2122            eprintln!(
2123                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2124            );
2125        }
2126        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2127        for (unit, metadata) in &cpp_template_metadata {
2128            cpp_template_families
2129                .entry(metadata.primary_fq_name.clone())
2130                .or_default()
2131                .push(unit.clone());
2132        }
2133        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
2134        // order above is a function of those hashes. Two mirrored headers can
2135        // declare one specialization; `select_template_specialization` treats
2136        // them as interchangeable and returns the family's first entry, so an
2137        // unsorted family made the reported declaration depend on the
2138        // workspace's absolute path and on unrelated files (#1836). Order the
2139        // family exactly as `build_visible_identifier_index` orders its
2140        // per-identifier candidate lists.
2141        for family in cpp_template_families.values_mut() {
2142            sort_lookup_units(family);
2143        }
2144        if report_stats {
2145            eprintln!(
2146                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2147                cpp_template_families.len(),
2148                cpp_template_families.values().map(Vec::len).sum::<usize>(),
2149                families_started.elapsed().as_millis(),
2150            );
2151            eprintln!(
2152                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2153                visible_by_file.len(),
2154                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2155                finalize_started.elapsed().as_millis(),
2156                visibility_started.elapsed().as_millis(),
2157            );
2158        }
2159        Self {
2160            cpp,
2161            token,
2162            visible_by_file,
2163            visible_by_identifier,
2164            global_field_internal_linkage,
2165            visible_source_files_by_root,
2166            alias_cells: Mutex::new(HashMap::default()),
2167            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2168            parser_alias_target_matches: RwLock::new(HashMap::default()),
2169            ordinary_type_import_cells: Mutex::new(HashMap::default()),
2170            project_using_index: OnceLock::new(),
2171            callable_reference_specs: Mutex::new(HashMap::default()),
2172            structured_include_fact_cells: Mutex::new(HashMap::default()),
2173            include_activation_cells: Mutex::new(HashMap::default()),
2174            compile_proven_guard_cells: Mutex::new(HashMap::default()),
2175            include_path_admission_cells: Mutex::new(HashMap::default()),
2176            conditional_include_projection_cells: Mutex::new(HashMap::default()),
2177            #[cfg(any(test, feature = "test-support"))]
2178            conditional_include_projection_index_build_count: AtomicUsize::new(0),
2179            #[cfg(any(test, feature = "test-support"))]
2180            conditional_include_projection_state_count: AtomicUsize::new(0),
2181            #[cfg(any(test, feature = "test-support"))]
2182            conditional_include_target_state_count: AtomicUsize::new(0),
2183            #[cfg(any(test, feature = "test-support"))]
2184            include_activation_build_count: AtomicUsize::new(0),
2185            #[cfg(any(test, feature = "test-support"))]
2186            using_donor_activation_count: AtomicUsize::new(0),
2187            #[cfg(any(test, feature = "test-support"))]
2188            using_namespace_lookup_count: AtomicUsize::new(0),
2189            #[cfg(any(test, feature = "test-support"))]
2190            using_name_candidate_inspection_count: AtomicUsize::new(0),
2191            #[cfg(any(test, feature = "test-support"))]
2192            callable_reference_spec_build_count: AtomicUsize::new(0),
2193            #[cfg(any(test, feature = "test-support"))]
2194            alias_source_parse_counts: Mutex::new(HashMap::default()),
2195            #[cfg(any(test, feature = "test-support"))]
2196            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2197            parser_alias_fallback_calls: AtomicUsize::new(0),
2198            parser_alias_fallback_files: AtomicUsize::new(0),
2199            parser_alias_source_parses: AtomicUsize::new(0),
2200            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2201            field_type_facts: Mutex::new(HashMap::default()),
2202            structured_alias_targets: Mutex::new(HashMap::default()),
2203            callable_comparables: Mutex::new(HashMap::default()),
2204            comparable_name_declarations: Mutex::new(HashMap::default()),
2205            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2206            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2207            precise_parent_cache: Mutex::new(HashMap::default()),
2208            c_tag_kind_cache: Mutex::new(HashMap::default()),
2209            c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2210            macro_event_cells: Mutex::new(HashMap::default()),
2211            macro_event_name_sets: Mutex::new(HashMap::default()),
2212            macro_include_protection_cells: Mutex::new(HashMap::default()),
2213            macro_environment_checkpoints: Mutex::new(HashMap::default()),
2214            macro_replacements: Mutex::new(HashMap::default()),
2215            macro_local_binding_templates: Mutex::new(HashMap::default()),
2216            macro_lexical_templates: Mutex::new(HashMap::default()),
2217            macro_replacement_bodies: Mutex::new(HashMap::default()),
2218            macro_type_parameters: Mutex::new(HashMap::default()),
2219            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2220            #[cfg(any(test, feature = "test-support"))]
2221            macro_replacement_parse_count: AtomicUsize::new(0),
2222            #[cfg(any(test, feature = "test-support"))]
2223            macro_event_application_count: AtomicUsize::new(0),
2224            #[cfg(any(test, feature = "test-support"))]
2225            macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2226            #[cfg(any(test, feature = "test-support"))]
2227            macro_environment_copy_count: AtomicUsize::new(0),
2228            #[cfg(any(test, feature = "test-support"))]
2229            macro_environment_request_count: AtomicUsize::new(0),
2230            cpp_template_metadata,
2231            cpp_template_families,
2232            #[cfg(any(test, feature = "test-support"))]
2233            qualified_candidate_inspections: AtomicUsize::new(0),
2234            #[cfg(any(test, feature = "test-support"))]
2235            target_preserving_type_resolution_count: AtomicUsize::new(0),
2236            #[cfg(any(test, feature = "test-support"))]
2237            visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2238            #[cfg(any(test, feature = "test-support"))]
2239            visibility_identifier_batch_count: visibility_stats.identifier_batches,
2240        }
2241    }
2242
2243    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2244        if file == target.source() {
2245            return true;
2246        }
2247        if self.global_field_has_internal_linkage(target) {
2248            return self
2249                .visible_source_files_by_root
2250                .get(file)
2251                .is_some_and(|sources| sources.contains(target.source()));
2252        }
2253        self.visible_by_file
2254            .get(file)
2255            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2256    }
2257
2258    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2259        self.global_field_internal_linkage
2260            .get(unit)
2261            .copied()
2262            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2263    }
2264
2265    pub fn call_arity_evidence(
2266        &self,
2267        file: &ProjectFile,
2268        call: Node<'_>,
2269        source: &str,
2270    ) -> CallArityEvidence {
2271        self.call_arity_evidence_at(file, call, source, call.start_byte())
2272    }
2273
2274    /// Argument-count evidence for a call whose macro environment is not the
2275    /// one at its own byte offset.
2276    ///
2277    /// A call recovered from a macro replacement lives in a sentinel parse of
2278    /// its own, so its node offsets say nothing about which macros are active.
2279    /// `environment_byte` names the position in `file` whose macro environment
2280    /// governs the call: the macro definition site for a replacement body.
2281    pub fn call_arity_evidence_at(
2282        &self,
2283        file: &ProjectFile,
2284        call: Node<'_>,
2285        source: &str,
2286        environment_byte: usize,
2287    ) -> CallArityEvidence {
2288        let Some(arguments) = call
2289            .child_by_field_name("arguments")
2290            .or_else(|| call.child_by_field_name("parameters"))
2291            .or_else(|| call.child_by_field_name("value"))
2292            .or_else(|| first_named_child_of_kind(call, "argument_list"))
2293            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2294        else {
2295            return CallArityEvidence::Exact(0);
2296        };
2297        let recovered_c_keyword_arguments =
2298            recovered_c_keyword_argument_count(file, call, arguments, source);
2299        let c_semantics = reference_uses_c_semantics(self.cpp, file);
2300        let arguments = argument_children(arguments)
2301            .flat_map(|argument| {
2302                recovered_c_new_expression_arguments(argument, c_semantics)
2303                    .map(Vec::from)
2304                    .unwrap_or_else(|| vec![argument])
2305            })
2306            .collect::<Vec<_>>();
2307        if arguments
2308            .iter()
2309            .all(|argument| !argument_shape_may_change_arity(*argument))
2310        {
2311            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2312        }
2313        let environment = self.macro_environment(file, environment_byte);
2314        let mut stack = Vec::new();
2315        let mut total = recovered_c_keyword_arguments;
2316        for argument in arguments {
2317            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2318                return CallArityEvidence::Unknown;
2319            }
2320            let CallArityEvidence::Exact(spread) =
2321                self.argument_arity_evidence(argument, source, &environment, &mut stack)
2322            else {
2323                return CallArityEvidence::Unknown;
2324            };
2325            total += spread;
2326        }
2327        CallArityEvidence::Exact(total)
2328    }
2329
2330    fn argument_arity_evidence(
2331        &self,
2332        argument: Node<'_>,
2333        source: &str,
2334        environment: &MacroEnvironment,
2335        stack: &mut Vec<(ProjectFile, usize)>,
2336    ) -> CallArityEvidence {
2337        let (name, invocation_arguments, function_like) = match argument.kind() {
2338            "identifier" => (node_text(argument, source), None, false),
2339            "call_expression" => {
2340                let Some(function) = argument.child_by_field_name("function") else {
2341                    return CallArityEvidence::Exact(1);
2342                };
2343                if function.kind() != "identifier" {
2344                    return CallArityEvidence::Exact(1);
2345                }
2346                let Some(arguments) = argument.child_by_field_name("arguments") else {
2347                    return CallArityEvidence::Exact(1);
2348                };
2349                (node_text(function, source), Some(arguments), true)
2350            }
2351            _ => return CallArityEvidence::Exact(1),
2352        };
2353        let Some(binding) = environment.binding(name) else {
2354            return if environment.unknown_names {
2355                CallArityEvidence::Unknown
2356            } else {
2357                CallArityEvidence::Exact(1)
2358            };
2359        };
2360        if !binding.is_exact() {
2361            return CallArityEvidence::Unknown;
2362        }
2363        match (&binding.definition, invocation_arguments, function_like) {
2364            (MacroDefinition::Object { replacement }, None, false) => self
2365                .replacement_arity_evidence(
2366                    replacement,
2367                    &[],
2368                    &[],
2369                    source,
2370                    environment,
2371                    stack,
2372                    binding,
2373                ),
2374            (
2375                MacroDefinition::Function {
2376                    parameters,
2377                    replacement,
2378                },
2379                Some(arguments),
2380                true,
2381            ) => {
2382                let actuals = argument_children(arguments).collect::<Vec<_>>();
2383                if actuals.len() != parameters.len() {
2384                    CallArityEvidence::Unknown
2385                } else {
2386                    self.replacement_arity_evidence(
2387                        replacement,
2388                        parameters,
2389                        &actuals,
2390                        source,
2391                        environment,
2392                        stack,
2393                        binding,
2394                    )
2395                }
2396            }
2397            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2398            _ => CallArityEvidence::Unknown,
2399        }
2400    }
2401
2402    #[allow(clippy::too_many_arguments)]
2403    fn replacement_arity_evidence(
2404        &self,
2405        replacement: &str,
2406        parameters: &[String],
2407        actuals: &[Node<'_>],
2408        actual_source: &str,
2409        environment: &MacroEnvironment,
2410        stack: &mut Vec<(ProjectFile, usize)>,
2411        binding: &MacroBinding,
2412    ) -> CallArityEvidence {
2413        let identity = (binding.source.clone(), binding.declaration_byte);
2414        if stack.contains(&identity) || replacement.trim().is_empty() {
2415            return CallArityEvidence::Unknown;
2416        }
2417        stack.push(identity);
2418        let parsed = self.parsed_macro_replacement(binding, replacement);
2419        let evidence = (|| {
2420            let ParsedMacroReplacement::Parsed {
2421                source: sentinel,
2422                tree,
2423            } = parsed.as_ref()
2424            else {
2425                return None;
2426            };
2427            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2428            let arguments = call.child_by_field_name("arguments")?;
2429            let mut total = 0usize;
2430            for argument in argument_children(arguments) {
2431                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2432                    return None;
2433                }
2434                if argument.kind() == "identifier"
2435                    && let Some(parameter_index) = parameters
2436                        .iter()
2437                        .position(|parameter| parameter == node_text(argument, sentinel))
2438                {
2439                    if !macro_expansion_shape_is_safe(
2440                        actuals[parameter_index],
2441                        actual_source,
2442                        &[],
2443                        environment,
2444                    ) {
2445                        return None;
2446                    }
2447                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2448                        actuals[parameter_index],
2449                        actual_source,
2450                        environment,
2451                        stack,
2452                    ) else {
2453                        return None;
2454                    };
2455                    total += spread;
2456                    continue;
2457                }
2458                let CallArityEvidence::Exact(spread) =
2459                    self.argument_arity_evidence(argument, sentinel, environment, stack)
2460                else {
2461                    return None;
2462                };
2463                total += spread;
2464            }
2465            Some(CallArityEvidence::Exact(total))
2466        })()
2467        .unwrap_or(CallArityEvidence::Unknown);
2468        stack.pop();
2469        evidence
2470    }
2471
2472    fn parsed_macro_replacement(
2473        &self,
2474        binding: &MacroBinding,
2475        replacement: &str,
2476    ) -> Arc<ParsedMacroReplacement> {
2477        let key = (binding.source.clone(), binding.declaration_byte);
2478        let mut cache = self
2479            .macro_replacements
2480            .lock()
2481            .expect("C++ macro replacement cache poisoned");
2482        if let Some(parsed) = cache.get(&key) {
2483            return Arc::clone(parsed);
2484        }
2485        #[cfg(any(test, feature = "test-support"))]
2486        self.macro_replacement_parse_count
2487            .fetch_add(1, Ordering::Relaxed);
2488        let source =
2489            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2490        let mut parser = Parser::new();
2491        let parsed = parser
2492            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2493            .ok()
2494            .and_then(|()| parser.parse(&source, None))
2495            .filter(|tree| !tree.root_node().has_error())
2496            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2497                ParsedMacroReplacement::Parsed { source, tree }
2498            });
2499        let parsed = Arc::new(parsed);
2500        cache.insert(key, Arc::clone(&parsed));
2501        parsed
2502    }
2503
2504    /// Recover a typed local declared by an active C function-like macro.
2505    ///
2506    /// This is intentionally narrower than macro expansion. The replacement
2507    /// must parse as one declaration, and the invocation must bind every
2508    /// formal parameter to one structured argument. That is sufficient for
2509    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
2510    /// can make the binding provisional without erasing its last known
2511    /// definition; an explicit conflicting definition still replaces it with
2512    /// Unsupported. Malformed and statement-producing macros also fail closed.
2513    pub fn function_macro_local_binding<'tree>(
2514        &self,
2515        file: &ProjectFile,
2516        statement: Node<'tree>,
2517        source: &str,
2518    ) -> Option<MacroLocalBinding<'tree>> {
2519        if !is_c_source_file(file) {
2520            return None;
2521        }
2522        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2523            return Some(binding);
2524        }
2525        let call = match statement.kind() {
2526            "call_expression" => statement,
2527            "expression_statement" if statement.named_child_count() == 1 => {
2528                statement.named_child(0)?
2529            }
2530            _ => return None,
2531        };
2532        if call.kind() != "call_expression" {
2533            return None;
2534        }
2535        let function = call.child_by_field_name("function")?;
2536        if function.kind() != "identifier" {
2537            return None;
2538        }
2539        let arguments = call.child_by_field_name("arguments")?;
2540        let actuals = argument_children(arguments).collect::<Vec<_>>();
2541        let environment = self.macro_environment(file, call.start_byte());
2542        let function_name = node_text(function, source);
2543        let binding = environment.binding(function_name)?;
2544        let MacroDefinition::Function {
2545            parameters,
2546            replacement,
2547        } = &binding.definition
2548        else {
2549            return None;
2550        };
2551        if actuals.len() != parameters.len() {
2552            return None;
2553        }
2554        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2555        let (type_name, type_node) = match &template.declared_type {
2556            MacroLocalBindingTypeTemplate::Parameter(index) => {
2557                let actual = *actuals.get(*index)?;
2558                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2559                    return None;
2560                }
2561                (node_text(actual, source).trim().to_string(), Some(actual))
2562            }
2563            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2564        };
2565        if type_name.is_empty() {
2566            return None;
2567        }
2568        Some(MacroLocalBinding {
2569            name: template.name.clone(),
2570            type_name,
2571            type_node,
2572            pointer_depth: template.pointer_depth,
2573            proven_unit: None,
2574        })
2575    }
2576
2577    /// Recover the typed receiver established by a C container macro
2578    /// assignment, such as `value = container_of(ptr, struct item, link)`.
2579    ///
2580    /// The macro definition's parsed replacement identifies the one formal
2581    /// parameter used in type position.  The invocation supplies the actual
2582    /// type node, which is then resolved through the ordinary visibility
2583    /// index.  Calls with no unique type-position parameter, an unresolved
2584    /// type, or an uncertain macro environment remain unproven.
2585    pub fn function_macro_container_binding<'tree>(
2586        &self,
2587        analyzer: &CppGraphSource<'_>,
2588        file: &ProjectFile,
2589        assignment: Node<'tree>,
2590        source: &str,
2591    ) -> Option<MacroLocalBinding<'tree>> {
2592        if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2593            return None;
2594        }
2595        let name_node = assignment.child_by_field_name("left")?;
2596        if name_node.kind() != "identifier" {
2597            return None;
2598        }
2599        let call = assignment.child_by_field_name("right")?;
2600        if call.kind() != "call_expression" {
2601            return None;
2602        }
2603        let function = call.child_by_field_name("function")?;
2604        if function.kind() != "identifier" {
2605            return None;
2606        }
2607        let arguments = call.child_by_field_name("arguments")?;
2608        let actuals = argument_children(arguments).collect::<Vec<_>>();
2609        let function_name = node_text(function, source);
2610        let environment = self.macro_environment(file, call.start_byte());
2611        let binding = environment.binding(function_name)?;
2612        if !binding.is_exact() {
2613            return None;
2614        }
2615        let MacroDefinition::Function {
2616            parameters,
2617            replacement,
2618        } = &binding.definition
2619        else {
2620            return None;
2621        };
2622        if actuals.len() != parameters.len() {
2623            return None;
2624        }
2625        let body = self.parsed_macro_replacement_body(
2626            &(binding.source.clone(), binding.declaration_byte),
2627            parameters,
2628            replacement,
2629        )?;
2630        let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2631        let type_argument = *actuals.get(type_parameter)?;
2632        let type_node = macro_type_argument_node(type_argument, source)?;
2633        let type_name = node_text(type_node, source).trim().to_string();
2634        if type_name.is_empty() {
2635            return None;
2636        }
2637        let explicit_tag = match node_text(type_argument, source) {
2638            "struct" => Some(CppCTagKind::Struct),
2639            "union" => Some(CppCTagKind::Union),
2640            _ => None,
2641        };
2642        let resolved_type = if let Some(tag) = explicit_tag {
2643            let candidates = self
2644                .visible_identifier_candidates(file, &type_name)
2645                .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2646                .collect::<Vec<_>>();
2647            self.resolve_type_candidates(
2648                analyzer,
2649                file,
2650                &candidates,
2651                TypeCandidateResolution::Canonical,
2652            )
2653            .ok()
2654        } else {
2655            self.resolve_type_node_result(file, type_node, source)
2656                .ok()
2657                .flatten()
2658        };
2659        let proven_unit = resolved_type?;
2660        Some(MacroLocalBinding {
2661            name: node_text(name_node, source).to_string(),
2662            type_name,
2663            type_node: Some(type_node),
2664            pointer_depth: 1,
2665            proven_unit: Some(proven_unit),
2666        })
2667    }
2668
2669    /// Recover the invocation-specific type of a replacement local visible
2670    /// at the selected source token.
2671    pub fn macro_local_binding_at<'tree>(
2672        &self,
2673        file: &ProjectFile,
2674        root: Node<'tree>,
2675        source: &str,
2676        start_byte: usize,
2677        end_byte: usize,
2678    ) -> Option<MacroLocalBinding<'tree>> {
2679        crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2680    }
2681
2682    /// Resolve the source-backed lexical macro declaration for one source
2683    /// range. The range may be a point range (`start..start + 1`) inside an
2684    /// identifier, which is the form used by goto-definition queries.
2685    pub fn macro_lexical_binding(
2686        &self,
2687        file: &ProjectFile,
2688        root: Node<'_>,
2689        source: &str,
2690        start_byte: usize,
2691        end_byte: usize,
2692    ) -> Option<MacroLexicalBinding> {
2693        crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2694    }
2695
2696    /// Enumerate source ranges whose lexical meaning comes from a
2697    /// function-like macro formal or replacement local. Definition spellings
2698    /// themselves are omitted; callers needing goto-definition on a spelling
2699    /// use [`Self::macro_lexical_binding`].
2700    pub fn macro_lexical_references(
2701        &self,
2702        file: &ProjectFile,
2703        root: Node<'_>,
2704        source: &str,
2705        max_references: usize,
2706        cancelled: impl FnMut() -> bool,
2707    ) -> MacroLexicalReferences {
2708        crate::graph::macro_lexical::all_references(
2709            || self,
2710            file,
2711            root,
2712            source,
2713            max_references,
2714            cancelled,
2715        )
2716    }
2717
2718    /// Return the source identity of the exact active function-like macro
2719    /// binding. The definition AST is hydrated by the lexical helper from
2720    /// this `(ProjectFile, declaration byte)` pair, so no parser node escapes
2721    /// the prepared source borrow.
2722    pub(crate) fn function_macro_binding_at(
2723        &self,
2724        file: &ProjectFile,
2725        name: &str,
2726        before_byte: usize,
2727    ) -> Option<(ProjectFile, usize)> {
2728        let environment = self.macro_environment(file, before_byte);
2729        let binding = environment.binding(name)?;
2730        if binding.is_exact()
2731            && matches!(
2732                binding.definition,
2733                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2734            )
2735        {
2736            return Some((binding.source.clone(), binding.declaration_byte));
2737        }
2738        // A definition under an unknown condition is still certain at a use
2739        // within that same branch. The checkpoint retains the event that made
2740        // the binding uncertain. Require that exact defining event, so an
2741        // intervening undef, include, or conditional redefinition cannot be
2742        // bypassed by a lexical search for an older macro.
2743        if binding.source != *file {
2744            return None;
2745        }
2746        let prepared = self.cpp.prepared_syntax(self.token, file)?;
2747        let root = prepared.tree().root_node();
2748        let source = prepared.source();
2749        let reference = root.descendant_for_byte_range(
2750            before_byte,
2751            before_byte.saturating_add(1).min(source.len()),
2752        )?;
2753        let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2754        let cell = self.macro_event_cell(file);
2755        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2756        let event = events
2757            .iter()
2758            .find(|event| event.byte() == binding.declaration_byte)?;
2759        let MacroEvent::Define {
2760            name: defined_name,
2761            binding: definition,
2762            conditionals,
2763            ..
2764        } = event
2765        else {
2766            return None;
2767        };
2768        if defined_name != name
2769            || conditionals.is_empty()
2770            || !conditionals
2771                .iter()
2772                .all(|condition| reference_conditions.contains(condition))
2773            || !matches!(
2774                definition.definition,
2775                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2776            )
2777        {
2778            return None;
2779        }
2780        Some((definition.source.clone(), definition.declaration_byte))
2781    }
2782
2783    /// Return the type argument at a C function-like macro invocation.
2784    ///
2785    /// Exact local macro definitions identify type formals from their parsed
2786    /// replacement. An unavailable definition is deliberately not inferred:
2787    /// C has no AST distinction between an external macro call and an
2788    /// ordinary call to an unindexed function.
2789    pub fn function_macro_type_argument<'tree>(
2790        &self,
2791        file: &ProjectFile,
2792        node: Node<'tree>,
2793        source: &str,
2794    ) -> Option<Node<'tree>> {
2795        if !is_c_source_file(file) {
2796            return None;
2797        }
2798        let (call, argument_index, argument) = c_function_macro_argument(node)?;
2799        let function = call.child_by_field_name("function")?;
2800        let function_name = node_text(function, source);
2801        if function_name.is_empty() {
2802            return None;
2803        }
2804        if !self.file_defines_macro_name(file, function_name)
2805            && !self
2806                .visible_identifier_candidates(file, function_name)
2807                .any(|candidate| candidate.is_macro())
2808        {
2809            return None;
2810        }
2811        let environment = self.macro_environment(file, call.start_byte());
2812        let binding = environment.binding(function_name)?;
2813        if !binding.is_exact() {
2814            return None;
2815        }
2816        let (parameters, replacement, variadic) = match &binding.definition {
2817            MacroDefinition::Function {
2818                parameters,
2819                replacement,
2820            } => (parameters, replacement, false),
2821            MacroDefinition::VariadicFunction {
2822                parameters,
2823                replacement,
2824            } => (parameters, replacement, true),
2825            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2826        };
2827        let actuals = call
2828            .child_by_field_name("arguments")
2829            .map(argument_children)
2830            .into_iter()
2831            .flatten()
2832            .collect::<Vec<_>>();
2833        let arity_matches = if variadic {
2834            actuals.len() >= parameters.len()
2835        } else {
2836            actuals.len() == parameters.len()
2837        };
2838        let type_parameters = self.macro_type_parameter_indices(
2839            &(binding.source.clone(), binding.declaration_byte),
2840            parameters,
2841            replacement,
2842        )?;
2843        if !arity_matches || !type_parameters.contains(&argument_index) {
2844            return None;
2845        }
2846        let type_node = macro_type_argument_node(argument, source)?;
2847        if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
2848            return None;
2849        }
2850        Some(type_node)
2851    }
2852
2853    fn macro_local_binding_template(
2854        &self,
2855        binding: &MacroBinding,
2856        parameters: &[String],
2857        replacement: &str,
2858    ) -> Option<Arc<MacroLocalBindingTemplate>> {
2859        let key = (binding.source.clone(), binding.declaration_byte);
2860        if let Some(template) = self
2861            .macro_local_binding_templates
2862            .lock()
2863            .expect("C++ macro local-binding cache poisoned")
2864            .get(&key)
2865        {
2866            return template.clone();
2867        }
2868        let template = (|| {
2869            let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2870            let sentinel = body.source.as_str();
2871            let statements = body.statements()?;
2872            if statements.named_child_count() != 1 {
2873                return None;
2874            }
2875            let declaration = statements.named_child(0)?;
2876            if declaration.kind() != "declaration" {
2877                return None;
2878            }
2879            let type_node = declaration
2880                .child_by_field_name("type")
2881                .or_else(|| first_type_child(declaration))?;
2882            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2883                let mut cursor = declaration.walk();
2884                declaration.named_children(&mut cursor).find_map(|child| {
2885                    if child.kind() == "init_declarator" {
2886                        child.child_by_field_name("declarator")
2887                    } else {
2888                        is_declarator_node(child).then_some(child)
2889                    }
2890                })
2891            })?;
2892            let name = extract_variable_name(declarator, sentinel)?;
2893            let pointer_depth = declared_name_indirection(declaration, type_node, &name, sentinel)?;
2894            let type_text = node_text(type_node, sentinel).trim();
2895            let declared_type = parameters
2896                .iter()
2897                .position(|parameter| parameter == type_text)
2898                .map(MacroLocalBindingTypeTemplate::Parameter)
2899                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2900            Some(Arc::new(MacroLocalBindingTemplate {
2901                name,
2902                declared_type,
2903                pointer_depth,
2904            }))
2905        })();
2906        self.macro_local_binding_templates
2907            .lock()
2908            .expect("C++ macro local-binding cache poisoned")
2909            .insert(key, template.clone());
2910        template
2911    }
2912
2913    /// The parsed replacement body of the function-like macro `definition`
2914    /// defines, or `None` when the replacement cannot be recovered exactly.
2915    ///
2916    /// `definition` is the defining `preproc_function_def` node in `file`, so
2917    /// the result describes that definition rather than whichever same-named
2918    /// macro a later reference resolves to.
2919    pub fn function_macro_replacement_body(
2920        &self,
2921        file: &ProjectFile,
2922        definition: Node<'_>,
2923        source: &str,
2924    ) -> Option<Arc<ParsedReplacementBody>> {
2925        debug_assert_eq!(definition.kind(), "preproc_function_def");
2926        let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
2927            MacroDefinition::Function {
2928                parameters,
2929                replacement,
2930            }
2931            | MacroDefinition::VariadicFunction {
2932                parameters,
2933                replacement,
2934            } => (parameters, replacement),
2935            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2936        };
2937        self.parsed_macro_replacement_body(
2938            &(file.clone(), definition.start_byte()),
2939            &parameters,
2940            &replacement,
2941        )
2942    }
2943
2944    /// Parse one function-like macro replacement inside the shared sentinel.
2945    ///
2946    /// The parse fails closed, and the failure is cached, whenever the
2947    /// sentinel tree carries an error or the replacement uses preprocessor
2948    /// syntax that has no C++ meaning. Token pasting and stringizing produce
2949    /// `ERROR` nodes; `__VA_ARGS__` parses as an ordinary identifier and is
2950    /// therefore rejected from the parsed tree instead of the source text.
2951    fn parsed_macro_replacement_body(
2952        &self,
2953        key: &(ProjectFile, usize),
2954        parameters: &[String],
2955        replacement: &str,
2956    ) -> Option<Arc<ParsedReplacementBody>> {
2957        if let Some(body) = self
2958            .macro_replacement_bodies
2959            .lock()
2960            .expect("C++ macro replacement body cache poisoned")
2961            .get(key)
2962        {
2963            return body.clone();
2964        }
2965        let body = (|| {
2966            if replacement.trim().is_empty() {
2967                return None;
2968            }
2969            let (source, tree, original_offsets) =
2970                Self::parse_macro_replacement_body(replacement, parameters)?;
2971            let body = ParsedReplacementBody {
2972                source,
2973                tree,
2974                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
2975                parameters: parameters.to_vec(),
2976                original_offsets,
2977            };
2978            body.statements()?;
2979            if body.expands_variadic_arguments() {
2980                return None;
2981            }
2982            Some(Arc::new(body))
2983        })();
2984        self.macro_replacement_bodies
2985            .lock()
2986            .expect("C++ macro replacement body cache poisoned")
2987            .insert(key.clone(), body.clone());
2988        body
2989    }
2990
2991    /// Parse a replacement while preserving a map from the parse buffer back
2992    /// to the original logical-line bytes. A block formal can occur where C
2993    /// requires a statement terminator (`if (condition) block`); the caller's
2994    /// actual block supplies that terminator only after macro substitution.
2995    /// Tree-sitter exposes the formal as an identifier inside recovery, so a
2996    /// semicolon is inserted at that AST-derived boundary for the fallback
2997    /// parse. No identifier text is scanned to find the insertion point.
2998    fn parse_macro_replacement_body(
2999        replacement: &str,
3000        parameters: &[String],
3001    ) -> Option<(String, Tree, Box<[usize]>)> {
3002        let normalized = normalize_macro_continuations(replacement);
3003        let parse = |replacement: &str| {
3004            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3005            let mut parser = Parser::new();
3006            parser
3007                .set_language(&tree_sitter_cpp::LANGUAGE.into())
3008                .ok()?;
3009            let tree = parser.parse(&source, None)?;
3010            Some((source, tree))
3011        };
3012        let (source, tree) = parse(&normalized)?;
3013        if !tree.root_node().has_error() {
3014            let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
3015            Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3016            return Some((source, tree, original_offsets.into_boxed_slice()));
3017        }
3018
3019        let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3020        let mut insertion_points = Vec::new();
3021        let mut stack = vec![tree.root_node()];
3022        while let Some(node) = stack.pop() {
3023            if matches!(
3024                node.kind(),
3025                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3026            ) && parameters
3027                .iter()
3028                .any(|parameter| parameter.as_str() == node_text(node, &source))
3029                && Self::macro_formal_needs_statement_separator(node)
3030            {
3031                let point = node.end_byte().saturating_sub(body_offset);
3032                if point <= normalized.len() && !insertion_points.contains(&point) {
3033                    insertion_points.push(point);
3034                }
3035            }
3036            push_named_children_reversed(node, &mut stack);
3037        }
3038        if insertion_points.is_empty() {
3039            return None;
3040        }
3041        insertion_points.sort_unstable();
3042        let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3043        let mut original_offsets =
3044            Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3045        let mut next_insertion = 0;
3046        for (index, byte) in normalized.bytes().enumerate() {
3047            recovered.push(byte);
3048            original_offsets.push(index);
3049            while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3050                recovered.push(b';');
3051                original_offsets.push(index + 1);
3052                next_insertion += 1;
3053            }
3054        }
3055        original_offsets.push(normalized.len());
3056        Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3057        let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3058        let (source, tree) = parse(&recovered)?;
3059        if tree.root_node().has_error() {
3060            return None;
3061        }
3062        Some((source, tree, original_offsets.into_boxed_slice()))
3063    }
3064
3065    /// Extend a replacement-origin map over the `; }` suffix appended by the
3066    /// sentinel parser. Nodes such as a declaration whose source omits its
3067    /// terminator include that synthetic semicolon; every suffix boundary
3068    /// still maps to the replacement's real end.
3069    fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3070        offsets.extend([replacement_end; 3]);
3071    }
3072
3073    /// A formal is statement-shaped when the recovered tree places it directly
3074    /// at a statement boundary. This is the grammar recovery produced for
3075    /// replacement forms such as `if (condition) block`; an identifier inside
3076    /// an expression or call argument must not receive an artificial `;`.
3077    fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3078        let mut current = node;
3079        let mut crossed_recovery = false;
3080        while let Some(parent) = current.parent() {
3081            if parent.is_error() {
3082                crossed_recovery = true;
3083                current = parent;
3084                continue;
3085            }
3086            if matches!(
3087                parent.kind(),
3088                "call_expression"
3089                    | "argument_list"
3090                    | "field_expression"
3091                    | "binary_expression"
3092                    | "unary_expression"
3093                    | "assignment_expression"
3094                    | "conditional_expression"
3095                    | "parenthesized_expression"
3096                    | "subscript_expression"
3097            ) {
3098                return false;
3099            }
3100            if parent.kind() == "expression_statement" {
3101                return parent.named_child_count() == 1;
3102            }
3103            if parent.kind() == "compound_statement" && current == node {
3104                return true;
3105            }
3106            if matches!(
3107                parent.kind(),
3108                "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3109            ) {
3110                return crossed_recovery
3111                    || parent.child_by_field_name("consequence") == Some(current);
3112            }
3113            if matches!(parent.kind(), "declaration" | "init_declarator") {
3114                return false;
3115            }
3116            current = parent;
3117        }
3118        false
3119    }
3120
3121    /// Identify fixed macro formals that retain an unambiguous type role even
3122    /// when an unrelated part of the replacement needs parser recovery.
3123    ///
3124    /// Variadic production macros commonly contain token pasting elsewhere in
3125    /// the body. The strict shared replacement parser must keep rejecting that
3126    /// body for expansion modeling, but a type declaration such as `TY col`
3127    /// remains usable when the node itself retains a structured type role.
3128    fn macro_type_parameter_indices(
3129        &self,
3130        key: &(ProjectFile, usize),
3131        parameters: &[String],
3132        replacement: &str,
3133    ) -> Option<Arc<[usize]>> {
3134        if let Some(indices) = self
3135            .macro_type_parameters
3136            .lock()
3137            .expect("C++ macro type-parameter cache poisoned")
3138            .get(key)
3139        {
3140            return indices.clone();
3141        }
3142        #[cfg(any(test, feature = "test-support"))]
3143        self.macro_replacement_parse_count
3144            .fetch_add(1, Ordering::Relaxed);
3145        let indices = (|| {
3146            if replacement.trim().is_empty() {
3147                return None;
3148            }
3149            // Keep this parser permissive. Type-role extraction predates the
3150            // replacement-body model and deliberately survives unrelated
3151            // token-pasting or recovery elsewhere in a production macro.
3152            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3153            let mut parser = Parser::new();
3154            parser
3155                .set_language(&tree_sitter_cpp::LANGUAGE.into())
3156                .ok()?;
3157            let tree = parser.parse(&source, None)?;
3158            let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3159            Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3160            let body = ParsedReplacementBody {
3161                source,
3162                tree,
3163                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3164                parameters: parameters.to_vec(),
3165                original_offsets: original_offsets.into_boxed_slice(),
3166            };
3167            macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3168        })();
3169        self.macro_type_parameters
3170            .lock()
3171            .expect("C++ macro type-parameter cache poisoned")
3172            .insert(key.clone(), indices.clone());
3173        indices
3174    }
3175
3176    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3177        let replacement = if node.kind() == "preproc_function_def" {
3178            function_macro_replacement_span(node, source)
3179                .and_then(|span| source.get(span))
3180                .map(str::to_owned)
3181                .or_else(|| {
3182                    node.child_by_field_name("value")
3183                        .map(|value| node_text(value, source).to_string())
3184                })
3185                .unwrap_or_default()
3186        } else {
3187            node.child_by_field_name("value")
3188                .map(|value| node_text(value, source).to_string())
3189                .unwrap_or_default()
3190        };
3191        if node.kind() == "preproc_def" {
3192            return MacroDefinition::Object { replacement };
3193        }
3194        let Some(parameters) = node.child_by_field_name("parameters") else {
3195            return MacroDefinition::Unsupported;
3196        };
3197        let variadic = (0..parameters.child_count()).any(|index| {
3198            parameters
3199                .child(index)
3200                .is_some_and(|child| child.kind() == "...")
3201        });
3202        let parameters = (0..parameters.named_child_count())
3203            .filter_map(|index| parameters.named_child(index))
3204            .map(|parameter| node_text(parameter, source).to_string())
3205            .collect::<Vec<_>>();
3206        if variadic {
3207            MacroDefinition::VariadicFunction {
3208                parameters,
3209                replacement,
3210            }
3211        } else {
3212            MacroDefinition::Function {
3213                parameters,
3214                replacement,
3215            }
3216        }
3217    }
3218
3219    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3220        self.macro_event_cells
3221            .lock()
3222            .expect("C++ macro event cache poisoned")
3223            .entry(file.clone())
3224            .or_default()
3225            .clone()
3226    }
3227
3228    fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3229        if let Some(names) = self
3230            .macro_event_name_sets
3231            .lock()
3232            .expect("C++ macro event-name cache poisoned")
3233            .get(file)
3234            .cloned()
3235        {
3236            return names.contains(name);
3237        }
3238        let cell = self.macro_event_cell(file);
3239        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3240        let names = Arc::new(
3241            events
3242                .iter()
3243                .filter_map(|event| match event {
3244                    MacroEvent::Define { name, .. } => Some(name.clone()),
3245                    MacroEvent::Undef { .. }
3246                    | MacroEvent::Include { .. }
3247                    | MacroEvent::Invalidate { .. } => None,
3248                })
3249                .collect(),
3250        );
3251        self.macro_event_name_sets
3252            .lock()
3253            .expect("C++ macro event-name cache poisoned")
3254            .insert(file.clone(), Arc::clone(&names));
3255        names.contains(name)
3256    }
3257
3258    fn macro_environment_checkpoint_cell(
3259        &self,
3260        file: &ProjectFile,
3261    ) -> MacroEnvironmentCheckpointCell {
3262        self.macro_environment_checkpoints
3263            .lock()
3264            .expect("C++ macro environment checkpoint cache poisoned")
3265            .entry(file.clone())
3266            .or_default()
3267            .clone()
3268    }
3269
3270    /// The environment of `file`'s macro events applied up to `before_byte`.
3271    pub fn macro_environment(
3272        &self,
3273        file: &ProjectFile,
3274        before_byte: usize,
3275    ) -> Arc<MacroEnvironment> {
3276        #[cfg(any(test, feature = "test-support"))]
3277        self.macro_environment_request_count
3278            .fetch_add(1, Ordering::Relaxed);
3279        let cell = self.macro_event_cell(file);
3280        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3281        let frontier = events.partition_point(|event| event.byte() < before_byte);
3282        let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3283        let checkpoints =
3284            checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3285        let checkpoint = checkpoints.at_or_before(frontier);
3286        if checkpoint.frontier == frontier {
3287            return Arc::clone(&checkpoint.environment);
3288        }
3289        #[cfg(any(test, feature = "test-support"))]
3290        self.macro_environment_copy_count
3291            .fetch_add(1, Ordering::Relaxed);
3292        let mut environment = checkpoint.environment.as_ref().clone();
3293        let mut include_stack = HashSet::from_iter([file.clone()]);
3294        for event in &events[checkpoint.frontier..frontier] {
3295            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3296        }
3297        Arc::new(environment)
3298    }
3299
3300    /// Apply `file`'s events once, keeping the environment at the prefixes
3301    /// [`MacroEnvironmentCheckpoints`] describes.
3302    fn build_macro_environment_checkpoints(
3303        &self,
3304        file: &ProjectFile,
3305        events: &[MacroEvent],
3306    ) -> MacroEnvironmentCheckpoints {
3307        #[cfg(any(test, feature = "test-support"))]
3308        self.macro_environment_checkpoint_build_count
3309            .fetch_add(1, Ordering::Relaxed);
3310        // The TU's build-proven defines hold from the first byte (#2011): they
3311        // are facts of the whole compile, so they seed the frontier-zero
3312        // checkpoint. A later explicit #undef event still overrides them
3313        // through `known_undefined_names`.
3314        let mut environment = MacroEnvironment {
3315            build_proven_defines: self
3316                .compile_proven_guards(file)
3317                .iter()
3318                .filter_map(|guard| match guard {
3319                    PreprocessorGuard::Defined(name) => Some(name.clone()),
3320                    _ => None,
3321                })
3322                .collect(),
3323            ..MacroEnvironment::default()
3324        };
3325        let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3326            frontier: 0,
3327            environment: Arc::new(environment.clone()),
3328        }];
3329        // Keep roughly one fixed stride's worth of checkpoints, while taking
3330        // a checkpoint at every event for event sets no larger than the fixed
3331        // stride. Generated C tables commonly have thousands of uses between
3332        // one #define and a trailing #undef; with a fixed stride those
3333        // identical frontier requests would all copy and replay the same
3334        // prefix.
3335        let checkpoint_stride = events
3336            .len()
3337            .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3338            .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3339        let mut include_stack = HashSet::from_iter([file.clone()]);
3340        for (index, event) in events.iter().enumerate() {
3341            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3342            let frontier = index + 1;
3343            if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3344                checkpoints.push(MacroEnvironmentCheckpoint {
3345                    frontier,
3346                    environment: Arc::new(environment.clone()),
3347                });
3348            }
3349        }
3350        MacroEnvironmentCheckpoints { checkpoints }
3351    }
3352
3353    /// Whether `name` is bound as a macro at `before_byte` in `file`,
3354    /// including a binding this environment cannot pin to one replacement
3355    /// (a conditional `#define`, or a function-like macro).
3356    ///
3357    /// [`Self::object_macro_replacement_at`] collapses every such binding to
3358    /// `None`, which is indistinguishable from "not a macro at all". A caller
3359    /// that must not read a macro token as an ordinary type name needs the two
3360    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
3361    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3362        self.macro_environment(file, before_byte)
3363            .binding(name)
3364            .is_some()
3365    }
3366
3367    pub fn macro_name_may_be_bound_at(
3368        &self,
3369        file: &ProjectFile,
3370        name: &str,
3371        before_byte: usize,
3372    ) -> bool {
3373        self.macro_environment(file, before_byte).may_bind(name)
3374    }
3375
3376    /// Whether the active macro binding at this reference is the requested
3377    /// indexed definition. Name equality alone is not enough because two
3378    /// headers can define the same macro for different translation units.
3379    pub fn macro_binding_matches_target_at(
3380        &self,
3381        analyzer: &CppGraphSource<'_>,
3382        file: &ProjectFile,
3383        name: &str,
3384        before_byte: usize,
3385        target: &CodeUnit,
3386    ) -> bool {
3387        let ranges = analyzer.ranges(target);
3388        let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3389        self.macro_binding_matches_target_declaration_at(
3390            file,
3391            name,
3392            before_byte,
3393            target.source(),
3394            &declaration_bytes,
3395        )
3396    }
3397
3398    /// Resolve declaration ranges once for inverse scans that test many call
3399    /// sites against one macro target. Walking a large generated syntax tree
3400    /// back to the same `#define` for every call site is otherwise quadratic
3401    /// in the number of top-level declarations.
3402    pub(crate) fn macro_declaration_bytes(
3403        &self,
3404        target: &CodeUnit,
3405        ranges: &[Range],
3406    ) -> Vec<usize> {
3407        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3408            return Vec::new();
3409        };
3410        ranges
3411            .iter()
3412            .filter_map(|range| {
3413                let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3414                while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3415                    node = node.parent()?;
3416                }
3417                Some(node.start_byte())
3418            })
3419            .collect()
3420    }
3421
3422    pub(crate) fn macro_binding_matches_target_declaration_at(
3423        &self,
3424        file: &ProjectFile,
3425        name: &str,
3426        before_byte: usize,
3427        target_source: &ProjectFile,
3428        target_declaration_bytes: &[usize],
3429    ) -> bool {
3430        let environment = self.macro_environment(file, before_byte);
3431        let Some(binding) = environment.binding(name) else {
3432            return false;
3433        };
3434        if binding.definition == MacroDefinition::Unsupported {
3435            return false;
3436        }
3437        // A normal header guard makes the replacement text conditional, but
3438        // it does not erase the definition site's source and byte identity.
3439        // Keep that identity even when expansion details are not exact.
3440        if binding.source != *target_source {
3441            return false;
3442        }
3443        target_declaration_bytes.contains(&binding.declaration_byte)
3444    }
3445
3446    /// Resolve an ordinary expression-position macro token at its exact byte.
3447    ///
3448    /// Calls and preprocessor-condition tokens have separate resolution
3449    /// surfaces. Declaration names, macro parameters, and labels are not
3450    /// references. Keeping that role policy here makes forward and both
3451    /// inverse graph builders consume the same activation verdict (#2093).
3452    pub fn resolve_ordinary_macro_reference(
3453        &self,
3454        analyzer: &CppGraphSource<'_>,
3455        file: &ProjectFile,
3456        node: Node<'_>,
3457        source: &str,
3458    ) -> OrdinaryMacroReferenceResolution {
3459        if !is_ordinary_macro_reference_node(node) {
3460            return OrdinaryMacroReferenceResolution::Missing;
3461        }
3462        let name = node_text(node, source);
3463        if name.is_empty() {
3464            return OrdinaryMacroReferenceResolution::Missing;
3465        }
3466        let visible = self
3467            .visible_identifier_candidates(file, name)
3468            .filter(|candidate| candidate.is_macro())
3469            .cloned()
3470            .collect::<Vec<_>>();
3471        let mut exact = Vec::new();
3472        for candidate in &visible {
3473            if self.macro_binding_matches_target_at(
3474                analyzer,
3475                file,
3476                name,
3477                node.start_byte(),
3478                candidate,
3479            ) && !exact
3480                .iter()
3481                .any(|existing| same_visible_symbol(existing, candidate))
3482            {
3483                exact.push(candidate.clone());
3484            }
3485        }
3486        match exact.len() {
3487            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3488            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3489            0 if !visible.is_empty()
3490                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3491            {
3492                OrdinaryMacroReferenceResolution::Ambiguous
3493            }
3494            0 => OrdinaryMacroReferenceResolution::Missing,
3495        }
3496    }
3497
3498    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
3499    ///
3500    /// The ordinary census deliberately skips every `ERROR` subtree. This
3501    /// separate, precision-only frontier admits only roles that retain enough
3502    /// structure for the C usage graph to interpret independently (#2089).
3503    /// Macro evidence comes from this visibility index at the exact byte; no
3504    /// source-text parsing or terminal-name fallback is used.
3505    pub fn recovered_c_reference_ranges(
3506        &self,
3507        file: &ProjectFile,
3508        root: Node<'_>,
3509        source: &str,
3510        limit: usize,
3511    ) -> RecoveredCReferenceRanges {
3512        if !is_c_source_file(file) {
3513            return RecoveredCReferenceRanges::Complete(Vec::new());
3514        }
3515        let mut ranges = Vec::new();
3516        let mut seen = HashSet::default();
3517        let mut stack = vec![(root, root.is_error())];
3518        while let Some((node, inside_error)) = stack.pop() {
3519            let inside_error = inside_error || node.is_error();
3520            if node.kind() == "preproc_arg" {
3521                // Tree-sitter keeps an object-like replacement opaque. The
3522                // inverse extractor uses the same parsed replacement helper;
3523                // retain its exact macro/type leaves for precision membership
3524                // even when the preprocessor node is outside ERROR recovery.
3525                let macro_value_kind = node.parent().and_then(|parent| {
3526                    (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3527                });
3528                if matches!(
3529                    macro_value_kind,
3530                    Some("preproc_def" | "preproc_function_def")
3531                ) {
3532                    let name = node_text(node, source);
3533                    if !name.is_empty()
3534                        && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3535                        && !push_recovered_c_range(
3536                            &mut ranges,
3537                            &mut seen,
3538                            node.start_byte(),
3539                            node.end_byte(),
3540                            node,
3541                            limit,
3542                        )
3543                    {
3544                        return RecoveredCReferenceRanges::LimitExceeded;
3545                    }
3546                }
3547                if macro_value_kind == Some("preproc_def") {
3548                    for reference in object_macro_replacement_type_references(node, source) {
3549                        for range in reference.component_ranges {
3550                            let visible = self
3551                                .visible_identifier_candidates(file, &source[range.clone()])
3552                                .any(|candidate| {
3553                                    candidate.is_class()
3554                                        || candidate.is_module()
3555                                        || is_type_alias(candidate)
3556                                });
3557                            if visible
3558                                && !push_recovered_c_range(
3559                                    &mut ranges,
3560                                    &mut seen,
3561                                    range.start,
3562                                    range.end,
3563                                    node,
3564                                    limit,
3565                                )
3566                            {
3567                                return RecoveredCReferenceRanges::LimitExceeded;
3568                            }
3569                        }
3570                    }
3571                }
3572            }
3573            if inside_error
3574                && recovered_c_reference_node(self, file, node, source)
3575                && !push_recovered_c_range(
3576                    &mut ranges,
3577                    &mut seen,
3578                    node.start_byte(),
3579                    node.end_byte(),
3580                    node,
3581                    limit,
3582                )
3583            {
3584                return RecoveredCReferenceRanges::LimitExceeded;
3585            }
3586            let mut cursor = node.walk();
3587            for child in node.named_children(&mut cursor) {
3588                stack.push((child, inside_error));
3589            }
3590        }
3591        ranges.sort_unstable();
3592        RecoveredCReferenceRanges::Complete(ranges)
3593    }
3594
3595    /// Whether this target is an indexed macro visible from this file.
3596    ///
3597    /// An unresolved conditional can make more than one same-name macro a
3598    /// possible active binding. Each possible target can keep the site as an
3599    /// unproven hit. A macro in an unrelated translation unit stays excluded.
3600    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3601        self.visible_identifier_candidates(file, target.identifier())
3602            .filter(|candidate| candidate.is_macro())
3603            .any(|candidate| {
3604                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3605            })
3606    }
3607
3608    pub fn object_macro_replacement_at(
3609        &self,
3610        file: &ProjectFile,
3611        name: &str,
3612        before_byte: usize,
3613    ) -> Option<String> {
3614        let environment = self.macro_environment(file, before_byte);
3615        let binding = environment.binding(name)?;
3616        if !binding.exact {
3617            return None;
3618        }
3619        match &binding.definition {
3620            MacroDefinition::Object { replacement } => Some(replacement.clone()),
3621            MacroDefinition::Function { .. }
3622            | MacroDefinition::VariadicFunction { .. }
3623            | MacroDefinition::Unsupported => None,
3624        }
3625    }
3626
3627    fn apply_macro_events(
3628        &self,
3629        file: &ProjectFile,
3630        before_byte: Option<usize>,
3631        environment: &mut MacroEnvironment,
3632        include_stack: &mut HashSet<ProjectFile>,
3633    ) {
3634        if !include_stack.insert(file.clone()) {
3635            return;
3636        }
3637        if self.cpp.prepared_syntax(self.token, file).is_none() {
3638            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3639            include_stack.remove(file);
3640            return;
3641        }
3642        match self.macro_include_protection(file) {
3643            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3644                Some(binding) if binding.is_exact() => {
3645                    include_stack.remove(file);
3646                    return;
3647                }
3648                Some(_) | None if environment.unknown_names => {
3649                    let mut ambiguous_seen = HashSet::default();
3650                    self.mark_macro_events_ambiguous(
3651                        file,
3652                        environment,
3653                        &mut ambiguous_seen,
3654                        file,
3655                        before_byte.unwrap_or_default(),
3656                    );
3657                    include_stack.remove(file);
3658                    return;
3659                }
3660                Some(_) => {
3661                    let mut ambiguous_seen = HashSet::default();
3662                    self.mark_macro_events_ambiguous(
3663                        file,
3664                        environment,
3665                        &mut ambiguous_seen,
3666                        file,
3667                        before_byte.unwrap_or_default(),
3668                    );
3669                    include_stack.remove(file);
3670                    return;
3671                }
3672                None => {}
3673            },
3674            MacroIncludeProtection::PragmaOnce => {
3675                if !environment.applied_pragma_once_files.insert(file.clone()) {
3676                    include_stack.remove(file);
3677                    return;
3678                }
3679                if environment.maybe_applied_pragma_once_files.remove(file) {
3680                    // A prior conditional include may already have consumed the pragma-once
3681                    // header. This unconditional include guarantees it is consumed now, but
3682                    // cannot prove whether its events occur before or after intervening local
3683                    // macro changes, so preserve the union as ambiguous.
3684                    let mut ambiguous_seen = HashSet::default();
3685                    environment.applied_pragma_once_files.remove(file);
3686                    self.mark_macro_events_ambiguous(
3687                        file,
3688                        environment,
3689                        &mut ambiguous_seen,
3690                        file,
3691                        before_byte.unwrap_or_default(),
3692                    );
3693                    environment.maybe_applied_pragma_once_files.remove(file);
3694                    environment.applied_pragma_once_files.insert(file.clone());
3695                    include_stack.remove(file);
3696                    return;
3697                }
3698            }
3699            MacroIncludeProtection::None => {}
3700        }
3701        let cell = self.macro_event_cell(file);
3702        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3703        for event in events {
3704            if before_byte.is_some_and(|limit| event.byte() >= limit) {
3705                break;
3706            }
3707            self.apply_macro_event(file, event, environment, include_stack);
3708        }
3709        include_stack.remove(file);
3710    }
3711
3712    fn apply_macro_event(
3713        &self,
3714        file: &ProjectFile,
3715        event: &MacroEvent,
3716        environment: &mut MacroEnvironment,
3717        include_stack: &mut HashSet<ProjectFile>,
3718    ) {
3719        #[cfg(any(test, feature = "test-support"))]
3720        self.macro_event_application_count
3721            .fetch_add(1, Ordering::Relaxed);
3722        match event {
3723            MacroEvent::Define {
3724                name,
3725                binding,
3726                conditionals,
3727                byte,
3728            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3729                Some(true) => environment.insert(name.clone(), binding.clone()),
3730                Some(false) => {}
3731                None => Self::merge_conditional_macro_definition(
3732                    environment,
3733                    name,
3734                    binding,
3735                    file,
3736                    *byte,
3737                ),
3738            },
3739            MacroEvent::Undef {
3740                name,
3741                conditionals,
3742                byte,
3743            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3744                Some(true) => environment.remove(name),
3745                Some(false) => {}
3746                None => {
3747                    if environment.binding(name).is_some() {
3748                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3749                    }
3750                }
3751            },
3752            MacroEvent::Include {
3753                targets,
3754                conditionals,
3755                byte,
3756            } => {
3757                let condition =
3758                    self.macro_event_condition_value(file, *byte, environment, conditionals);
3759                if condition == Some(false) {
3760                    return;
3761                }
3762                if targets.is_empty() {
3763                    environment.mark_unknown_names(file, *byte);
3764                    return;
3765                }
3766                if condition.is_none() || targets.len() > 1 {
3767                    let mut ambiguous_seen = HashSet::default();
3768                    for target in targets {
3769                        self.mark_macro_events_ambiguous(
3770                            target,
3771                            environment,
3772                            &mut ambiguous_seen,
3773                            file,
3774                            *byte,
3775                        );
3776                    }
3777                } else if let Some(target) = targets.first() {
3778                    self.apply_macro_events(target, None, environment, include_stack);
3779                }
3780            }
3781            MacroEvent::Invalidate { byte } => {
3782                for binding in environment.bindings.values_mut() {
3783                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
3784                }
3785            }
3786        }
3787    }
3788
3789    /// Evaluate the structured conditional path that owns one macro event.
3790    ///
3791    /// `Some(true)` and `Some(false)` are proofs from exact macro bindings at
3792    /// this source byte. `None` preserves the old conditional merge when a
3793    /// build/configuration input or an unsupported expression is involved.
3794    ///
3795    /// `conditionals` is the event's own [`OwningPreprocessorConditionals`]:
3796    /// which ancestors structurally own the event was decided when the events
3797    /// were collected, so all this walk does is find those nodes again and ask
3798    /// the environment what their conditions are worth here.
3799    fn macro_event_condition_value(
3800        &self,
3801        file: &ProjectFile,
3802        event_byte: usize,
3803        environment: &MacroEnvironment,
3804        conditionals: &OwningPreprocessorConditionals,
3805    ) -> Option<bool> {
3806        if conditionals.is_empty() {
3807            return Some(true);
3808        }
3809        let prepared = self.cpp.prepared_syntax(self.token, file)?;
3810        let source = prepared.source();
3811        let root = prepared.tree().root_node();
3812        let descendant = root.descendant_for_byte_range(
3813            event_byte,
3814            event_byte.saturating_add(1).min(source.len()),
3815        )?;
3816        let mut unknown = false;
3817        let mut current = descendant.parent();
3818        while let Some(conditional) = current {
3819            if matches!(
3820                conditional.kind(),
3821                "preproc_if" | "preproc_ifdef" | "preproc_elif"
3822            ) && conditionals.contains(&conditional.start_byte())
3823            {
3824                let mut value = match conditional.kind() {
3825                    "preproc_ifdef" => {
3826                        let name = conditional.child_by_field_name("name")?;
3827                        let defined =
3828                            self.macro_name_defined_value(environment, node_text(name, source));
3829                        match conditional.child(0)?.kind() {
3830                            "#ifdef" => defined,
3831                            "#ifndef" => defined.map(|defined| !defined),
3832                            _ => None,
3833                        }
3834                    }
3835                    "preproc_if" | "preproc_elif" => conditional
3836                        .child_by_field_name("condition")
3837                        .and_then(|condition| {
3838                            self.preprocessor_integer_value(
3839                                condition,
3840                                source,
3841                                environment,
3842                                &mut Vec::new(),
3843                                0,
3844                            )
3845                        })
3846                        .map(|value| value != 0),
3847                    _ => unreachable!(),
3848                };
3849                if conditional
3850                    .child_by_field_name("alternative")
3851                    .is_some_and(|alternative| {
3852                        alternative.start_byte() <= descendant.start_byte()
3853                            && descendant.end_byte() <= alternative.end_byte()
3854                    })
3855                {
3856                    value = value.map(|value| !value);
3857                }
3858                match value {
3859                    Some(true) => {}
3860                    Some(false) => return Some(false),
3861                    None => unknown = true,
3862                }
3863            }
3864            current = conditional.parent();
3865        }
3866        (!unknown).then_some(true)
3867    }
3868
3869    fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
3870        if environment.known_undefined_names.contains(name) {
3871            return Some(false);
3872        }
3873        if let Some(binding) = environment.binding(name) {
3874            return binding.is_exact().then_some(true);
3875        }
3876        environment
3877            .build_proven_defines
3878            .contains(name)
3879            .then_some(true)
3880    }
3881
3882    fn preprocessor_integer_value(
3883        &self,
3884        expression: Node<'_>,
3885        source: &str,
3886        environment: &MacroEnvironment,
3887        expansion_stack: &mut Vec<(ProjectFile, usize)>,
3888        depth: usize,
3889    ) -> Option<i128> {
3890        // Macro replacement graphs can cycle. This explicit bound makes the
3891        // otherwise recursive AST evaluation stack-safe for hostile input.
3892        if depth >= 64 {
3893            return None;
3894        }
3895        match expression.kind() {
3896            "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
3897            "identifier" | "type_identifier" => {
3898                let binding = environment.binding(node_text(expression, source))?;
3899                if !binding.is_exact() {
3900                    return None;
3901                }
3902                let MacroDefinition::Object { replacement } = &binding.definition else {
3903                    return None;
3904                };
3905                let identity = (binding.source.clone(), binding.declaration_byte);
3906                if expansion_stack.contains(&identity) {
3907                    return None;
3908                }
3909                expansion_stack.push(identity);
3910                let parsed = self.parsed_macro_replacement(binding, replacement);
3911                let value = match parsed.as_ref() {
3912                    ParsedMacroReplacement::Parsed {
3913                        source: replacement_source,
3914                        tree,
3915                    } => first_descendant_of_kind(tree.root_node(), "call_expression")
3916                        .and_then(|call| call.child_by_field_name("arguments"))
3917                        .and_then(|arguments| argument_children(arguments).next())
3918                        .and_then(|argument| {
3919                            self.preprocessor_integer_value(
3920                                argument,
3921                                replacement_source,
3922                                environment,
3923                                expansion_stack,
3924                                depth + 1,
3925                            )
3926                        }),
3927                    ParsedMacroReplacement::Unsupported => None,
3928                };
3929                expansion_stack.pop();
3930                value
3931            }
3932            "preproc_defined" => {
3933                let mut cursor = expression.walk();
3934                let name = expression
3935                    .named_children(&mut cursor)
3936                    .find(|child| child.kind() == "identifier")?;
3937                self.macro_name_defined_value(environment, node_text(name, source))
3938                    .map(i128::from)
3939            }
3940            "parenthesized_expression" => expression.named_child(0).and_then(|child| {
3941                self.preprocessor_integer_value(
3942                    child,
3943                    source,
3944                    environment,
3945                    expansion_stack,
3946                    depth + 1,
3947                )
3948            }),
3949            "unary_expression" => {
3950                let operator = expression.child_by_field_name("operator")?.kind();
3951                let argument = expression.child_by_field_name("argument")?;
3952                let value = self.preprocessor_integer_value(
3953                    argument,
3954                    source,
3955                    environment,
3956                    expansion_stack,
3957                    depth + 1,
3958                )?;
3959                match operator {
3960                    "+" => Some(value),
3961                    "-" => value.checked_neg(),
3962                    "!" => Some(i128::from(value == 0)),
3963                    "~" => Some(!value),
3964                    _ => None,
3965                }
3966            }
3967            "binary_expression" => {
3968                let left = self.preprocessor_integer_value(
3969                    expression.child_by_field_name("left")?,
3970                    source,
3971                    environment,
3972                    expansion_stack,
3973                    depth + 1,
3974                )?;
3975                let right = self.preprocessor_integer_value(
3976                    expression.child_by_field_name("right")?,
3977                    source,
3978                    environment,
3979                    expansion_stack,
3980                    depth + 1,
3981                )?;
3982                match expression.child_by_field_name("operator")?.kind() {
3983                    "+" => left.checked_add(right),
3984                    "-" => left.checked_sub(right),
3985                    "*" => left.checked_mul(right),
3986                    "/" => left.checked_div(right),
3987                    "%" => left.checked_rem(right),
3988                    "<<" => u32::try_from(right)
3989                        .ok()
3990                        .and_then(|shift| left.checked_shl(shift)),
3991                    ">>" => u32::try_from(right)
3992                        .ok()
3993                        .and_then(|shift| left.checked_shr(shift)),
3994                    "<" => Some(i128::from(left < right)),
3995                    "<=" => Some(i128::from(left <= right)),
3996                    ">" => Some(i128::from(left > right)),
3997                    ">=" => Some(i128::from(left >= right)),
3998                    "==" => Some(i128::from(left == right)),
3999                    "!=" => Some(i128::from(left != right)),
4000                    "&" => Some(left & right),
4001                    "|" => Some(left | right),
4002                    "^" => Some(left ^ right),
4003                    "&&" => Some(i128::from(left != 0 && right != 0)),
4004                    "||" => Some(i128::from(left != 0 || right != 0)),
4005                    _ => None,
4006                }
4007            }
4008            _ => None,
4009        }
4010    }
4011
4012    fn mark_macro_events_ambiguous(
4013        &self,
4014        file: &ProjectFile,
4015        environment: &mut MacroEnvironment,
4016        include_stack: &mut HashSet<ProjectFile>,
4017        conditional_file: &ProjectFile,
4018        conditional_byte: usize,
4019    ) {
4020        if !include_stack.insert(file.clone()) {
4021            return;
4022        }
4023        if self.cpp.prepared_syntax(self.token, file).is_none() {
4024            environment.mark_unknown_names(conditional_file, conditional_byte);
4025            return;
4026        }
4027        match self.macro_include_protection(file) {
4028            MacroIncludeProtection::MacroGuard(guard) => {
4029                if environment
4030                    .binding(&guard)
4031                    .is_some_and(MacroBinding::is_exact)
4032                {
4033                    return;
4034                }
4035            }
4036            MacroIncludeProtection::PragmaOnce => {
4037                if environment.applied_pragma_once_files.contains(file) {
4038                    return;
4039                }
4040                environment
4041                    .maybe_applied_pragma_once_files
4042                    .insert(file.clone());
4043            }
4044            MacroIncludeProtection::None => {}
4045        }
4046        let cell = self.macro_event_cell(file);
4047        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4048        for event in events {
4049            #[cfg(any(test, feature = "test-support"))]
4050            self.macro_event_application_count
4051                .fetch_add(1, Ordering::Relaxed);
4052            match event {
4053                MacroEvent::Define { name, binding, .. } => {
4054                    Self::merge_conditional_macro_definition(
4055                        environment,
4056                        name,
4057                        binding,
4058                        conditional_file,
4059                        conditional_byte,
4060                    );
4061                }
4062                MacroEvent::Undef { name, .. } => {
4063                    if environment.binding(name).is_some() {
4064                        environment.insert(
4065                            name.clone(),
4066                            MacroBinding::ambiguous(conditional_file, conditional_byte),
4067                        );
4068                    } else {
4069                        environment.remove_known_undefined(name);
4070                    }
4071                }
4072                MacroEvent::Include { targets, .. } => {
4073                    if targets.is_empty() {
4074                        environment.mark_unknown_names(conditional_file, conditional_byte);
4075                        continue;
4076                    }
4077                    for target in targets {
4078                        self.mark_macro_events_ambiguous(
4079                            target,
4080                            environment,
4081                            include_stack,
4082                            conditional_file,
4083                            conditional_byte,
4084                        );
4085                    }
4086                }
4087                MacroEvent::Invalidate { .. } => {
4088                    for binding in environment.bindings.values_mut() {
4089                        *binding = MacroBinding::uncertain_from(
4090                            binding,
4091                            conditional_file,
4092                            conditional_byte,
4093                        );
4094                    }
4095                }
4096            }
4097        }
4098    }
4099
4100    fn merge_conditional_macro_definition(
4101        environment: &mut MacroEnvironment,
4102        name: &str,
4103        possible_binding: &MacroBinding,
4104        conditional_file: &ProjectFile,
4105        conditional_byte: usize,
4106    ) {
4107        // A conditional include can revisit an already-active guarded header.
4108        // If the possible branch defines the exact same macro, both outcomes
4109        // leave the binding unchanged; degrading it to Unknown would discard
4110        // proof because of an unrelated unresolved macro name (#2092).
4111        if environment.binding(name).is_some_and(|current| {
4112            current.definition != MacroDefinition::Unsupported
4113                && current.definition == possible_binding.definition
4114        }) {
4115            return;
4116        }
4117        environment.insert(
4118            name.to_string(),
4119            MacroBinding::ambiguous(conditional_file, conditional_byte),
4120        );
4121    }
4122
4123    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4124        let cell = self
4125            .macro_include_protection_cells
4126            .lock()
4127            .expect("C++ include protection cache poisoned")
4128            .entry(file.clone())
4129            .or_default()
4130            .clone();
4131        cell.get_or_init(|| {
4132            self.cpp.prepared_syntax(self.token, file).map_or(
4133                MacroIncludeProtection::None,
4134                |prepared| {
4135                    top_level_macro_include_protection(
4136                        prepared.tree().root_node(),
4137                        prepared.source(),
4138                    )
4139                },
4140            )
4141        })
4142        .clone()
4143    }
4144
4145    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4146        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4147            return Vec::new();
4148        };
4149        let source = prepared.source();
4150        let mut events = Vec::new();
4151        let root = prepared.tree().root_node();
4152        let mut stack = vec![root];
4153        while let Some(node) = stack.pop() {
4154            match node.kind() {
4155                "preproc_def" | "preproc_function_def" => {
4156                    let Some(name) = node.child_by_field_name("name") else {
4157                        continue;
4158                    };
4159                    let name = node_text(name, source).to_string();
4160                    events.push(MacroEvent::Define {
4161                        name,
4162                        binding: MacroBinding {
4163                            source: file.clone(),
4164                            declaration_byte: node.start_byte(),
4165                            definition: Self::decode_macro_definition(node, source),
4166                            exact: true,
4167                        },
4168                        byte: node.start_byte(),
4169                        conditionals: owning_preprocessor_conditionals(root, node, source),
4170                    });
4171                    continue;
4172                }
4173                "preproc_include" => {
4174                    let Some(path) = node.child_by_field_name("path") else {
4175                        events.push(MacroEvent::Include {
4176                            targets: Vec::new(),
4177                            byte: node.start_byte(),
4178                            conditionals: owning_preprocessor_conditionals(root, node, source),
4179                        });
4180                        continue;
4181                    };
4182                    let include = structured_include_path(path, source);
4183                    let targets = include.map_or_else(Vec::new, |include| {
4184                        resolve_include_targets_with_index(
4185                            file,
4186                            include,
4187                            self.cpp.include_target_index(),
4188                        )
4189                    });
4190                    // An include no indexed file can answer -- its final path component names
4191                    // no analyzable workspace file -- crosses into sources this index does not
4192                    // hold, so it cannot hide indexed macro state and must not poison every
4193                    // later local macro binding. The include's spelling does not decide that:
4194                    // a quoted include falls back to the same search path an angle include
4195                    // uses, and esphome's `core/log.h` reaches every component with
4196                    // `#include "WString.h"`, an Arduino header absent from the tree (#3057).
4197                    // A computed include, and a spelling some indexed file name does match
4198                    // without resolving to one target, may still hide indexed macro state and
4199                    // therefore fail closed.
4200                    if targets.is_empty()
4201                        && include.is_some_and(|include| {
4202                            !self.cpp.include_target_index().names_indexed_file(include)
4203                        })
4204                    {
4205                        continue;
4206                    }
4207                    events.push(MacroEvent::Include {
4208                        targets,
4209                        byte: node.start_byte(),
4210                        conditionals: owning_preprocessor_conditionals(root, node, source),
4211                    });
4212                    continue;
4213                }
4214                "preproc_call" => {
4215                    let Some(directive) = node.child_by_field_name("directive") else {
4216                        continue;
4217                    };
4218                    if node_text(directive, source) != "#undef" {
4219                        continue;
4220                    }
4221                    let name = node
4222                        .child_by_field_name("argument")
4223                        .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
4224                    if let Some(name) = name {
4225                        events.push(MacroEvent::Undef {
4226                            name,
4227                            byte: node.start_byte(),
4228                            conditionals: owning_preprocessor_conditionals(root, node, source),
4229                        });
4230                    } else {
4231                        events.push(MacroEvent::Invalidate {
4232                            byte: node.start_byte(),
4233                        });
4234                    }
4235                    continue;
4236                }
4237                _ => {}
4238            }
4239            push_named_children_reversed(node, &mut stack);
4240        }
4241        events.sort_by_key(MacroEvent::byte);
4242        events
4243    }
4244
4245    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4246        self.ordinary_type_import_cells
4247            .lock()
4248            .expect("C++ ordinary type import cache poisoned")
4249            .entry(file.clone())
4250            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4251            .clone()
4252    }
4253
4254    pub fn project_using_index(
4255        &self,
4256        build: impl FnOnce() -> ProjectUsingIndex,
4257    ) -> &ProjectUsingIndex {
4258        self.project_using_index.get_or_init(build)
4259    }
4260
4261    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4262        let mut files = self
4263            .visible_source_files_by_root
4264            .values()
4265            .flatten()
4266            .cloned()
4267            .collect::<HashSet<_>>()
4268            .into_iter()
4269            .collect::<Vec<_>>();
4270        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4271        files
4272    }
4273
4274    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4275        self.visible_source_files_by_root
4276            .get(root)
4277            .is_some_and(|files| files.contains(source))
4278    }
4279
4280    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4281        let cached = self
4282            .visible_parser_alias_name_sets
4283            .read()
4284            .expect("visible parser alias-name cache poisoned")
4285            .get(file)
4286            .cloned();
4287        let cell = if let Some(cached) = cached {
4288            cached
4289        } else {
4290            let mut cells = self
4291                .visible_parser_alias_name_sets
4292                .write()
4293                .expect("visible parser alias-name cache poisoned");
4294            Arc::clone(
4295                cells
4296                    .entry(file.clone())
4297                    .or_insert_with(|| Arc::new(OnceLock::new())),
4298            )
4299        };
4300        cell.get_or_init(|| {
4301            #[cfg(any(test, feature = "test-support"))]
4302            self.visible_parser_alias_name_set_build_count
4303                .fetch_add(1, Ordering::Relaxed);
4304            let mut names = HashSet::default();
4305            let visible_files = self
4306                .visible_source_files_by_root
4307                .get(file)
4308                .cloned()
4309                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4310            for visible_file in visible_files {
4311                let aliases = {
4312                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4313                    Arc::clone(
4314                        cells
4315                            .entry(visible_file.clone())
4316                            .or_insert_with(|| Arc::new(OnceLock::new())),
4317                    )
4318                };
4319                for alias in aliases
4320                    .get_or_init(|| {
4321                        self.parser_alias_source_parses
4322                            .fetch_add(1, Ordering::Relaxed);
4323                        #[cfg(any(test, feature = "test-support"))]
4324                        {
4325                            *self
4326                                .alias_source_parse_counts
4327                                .lock()
4328                                .expect("alias source parse count lock")
4329                                .entry(visible_file.clone())
4330                                .or_default() += 1;
4331                        }
4332                        aliases_from_prepared_source(self.cpp, self.token, &visible_file)
4333                            .into_boxed_slice()
4334                    })
4335                    .iter()
4336                {
4337                    names.insert(alias.name.clone());
4338                }
4339            }
4340            names
4341        })
4342        .contains(name)
4343    }
4344
4345    pub fn parser_alias_name_may_resolve_to_target(
4346        &self,
4347        file: &ProjectFile,
4348        alias_name: &str,
4349        target: &CodeUnit,
4350    ) -> bool {
4351        let started = std::time::Instant::now();
4352        self.parser_alias_fallback_calls
4353            .fetch_add(1, Ordering::Relaxed);
4354        let key = (
4355            file.clone(),
4356            alias_name.to_string(),
4357            logical_symbol_key(target),
4358        );
4359        let cached = self
4360            .parser_alias_target_matches
4361            .read()
4362            .expect("parser alias target-match cache poisoned")
4363            .get(&key)
4364            .cloned();
4365        let cell = if let Some(cached) = cached {
4366            cached
4367        } else {
4368            let mut cells = self
4369                .parser_alias_target_matches
4370                .write()
4371                .expect("parser alias target-match cache poisoned");
4372            Arc::clone(
4373                cells
4374                    .entry(key)
4375                    .or_insert_with(|| Arc::new(OnceLock::new())),
4376            )
4377        };
4378        let matched = *cell.get_or_init(|| match self.visible_source_files_by_root.get(file) {
4379            None => {
4380                self.parser_alias_fallback_files
4381                    .fetch_add(1, Ordering::Relaxed);
4382                self.file_alias_matches(self.cpp, file, alias_name, target)
4383            }
4384            Some(visible_files) => visible_files.iter().any(|visible_file| {
4385                self.parser_alias_fallback_files
4386                    .fetch_add(1, Ordering::Relaxed);
4387                self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4388            }),
4389        });
4390        self.parser_alias_fallback_elapsed_micros.fetch_add(
4391            started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4392            Ordering::Relaxed,
4393        );
4394        matched
4395    }
4396
4397    fn file_alias_matches(
4398        &self,
4399        cpp: &dyn CppSource,
4400        file: &ProjectFile,
4401        alias_name: &str,
4402        target: &CodeUnit,
4403    ) -> bool {
4404        let cell = {
4405            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4406            Arc::clone(
4407                cells
4408                    .entry(file.clone())
4409                    .or_insert_with(|| Arc::new(OnceLock::new())),
4410            )
4411        };
4412        cell.get_or_init(|| {
4413            self.parser_alias_source_parses
4414                .fetch_add(1, Ordering::Relaxed);
4415            #[cfg(any(test, feature = "test-support"))]
4416            {
4417                *self
4418                    .alias_source_parse_counts
4419                    .lock()
4420                    .expect("alias source parse count lock")
4421                    .entry(file.clone())
4422                    .or_default() += 1;
4423            }
4424            aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
4425        })
4426        .iter()
4427        .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
4428    }
4429
4430    fn callable_arities_for_target(
4431        &self,
4432        analyzer: &CppGraphSource<'_>,
4433        cpp: &dyn CppSource,
4434        file: &ProjectFile,
4435        prepared: &PreparedSyntaxTree,
4436        spec: &TargetSpec,
4437    ) -> Vec<ActivatedCallableArity> {
4438        let Some(signature) = spec.target.signature() else {
4439            return Vec::new();
4440        };
4441        let Some(candidates) = self
4442            .visible_by_identifier
4443            .get(file)
4444            .and_then(|by_name| by_name.get(&spec.member_name))
4445        else {
4446            return Vec::new();
4447        };
4448        let differing_candidates = candidates
4449            .iter()
4450            .filter(|candidate| {
4451                candidate.is_function()
4452                    && candidate.fq_name() == spec.target.fq_name()
4453                    && candidate.signature() == Some(signature)
4454            })
4455            .filter_map(|candidate| {
4456                analyzer
4457                    .signature_metadata(candidate)
4458                    .into_iter()
4459                    .find_map(|metadata| metadata.callable_arity())
4460                    .filter(|arity| Some(*arity) != spec.callable_arity)
4461                    .map(|arity| (candidate, arity))
4462            })
4463            .collect::<Vec<_>>();
4464        if differing_candidates.is_empty() {
4465            return Vec::new();
4466        }
4467        let mut arities = Vec::with_capacity(differing_candidates.len());
4468        // The activation ranges here describe the whole file rather than one
4469        // reference, so there is no reference guard environment to consult.
4470        let reference = CallableReferenceContext {
4471            file,
4472            position: None,
4473        };
4474        for (candidate, candidate_arity) in differing_candidates {
4475            let declaration_activation = if candidate.source() == file {
4476                callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
4477            } else {
4478                cpp.prepared_syntax(self.token, candidate.source())
4479                    .and_then(|syntax| {
4480                        callable_declaration_activation_in_file(
4481                            analyzer,
4482                            syntax.as_ref(),
4483                            candidate,
4484                            &reference,
4485                        )
4486                    })
4487            };
4488            let Some(declaration_activation) = declaration_activation else {
4489                continue;
4490            };
4491            let activation_byte = if candidate.source() == file {
4492                Some(declaration_activation)
4493            } else {
4494                self.include_activation_for_source(cpp, file, prepared, candidate.source())
4495            };
4496            if let Some(activation_byte) = activation_byte {
4497                arities.push(ActivatedCallableArity {
4498                    activation_byte,
4499                    arity: candidate_arity,
4500                });
4501            }
4502        }
4503        arities
4504    }
4505
4506    fn callable_parameter_macro_arity(
4507        &self,
4508        target: &CodeUnit,
4509        signature: Option<&str>,
4510    ) -> Option<CallableArity> {
4511        let parameter_types = cpp_signature_param_types(signature?)?;
4512        let [macro_name] = parameter_types.as_slice() else {
4513            return None;
4514        };
4515        if macro_name.is_empty()
4516            || !macro_name
4517                .chars()
4518                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4519        {
4520            return None;
4521        }
4522        let cache_key = (target.source().clone(), macro_name.clone());
4523        if let Some(cached) = self
4524            .callable_parameter_macro_arities
4525            .lock()
4526            .expect("C++ callable parameter-macro arity cache poisoned")
4527            .get(&cache_key)
4528            .copied()
4529        {
4530            return cached;
4531        }
4532        let mut visible_files = HashSet::default();
4533        collect_include_closure(
4534            &self.cpp_source(),
4535            self.cpp.include_target_index(),
4536            target.source(),
4537            &mut visible_files,
4538            None,
4539        );
4540        let mut arities = Vec::new();
4541        for visible_file in visible_files {
4542            let cell = self.macro_event_cell(&visible_file);
4543            for event in
4544                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4545            {
4546                let MacroEvent::Define { name, binding, .. } = event else {
4547                    continue;
4548                };
4549                if name != macro_name {
4550                    continue;
4551                }
4552                let MacroDefinition::Object { replacement } = &binding.definition else {
4553                    continue;
4554                };
4555                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4556                    continue;
4557                };
4558                if !arities.contains(&arity) {
4559                    arities.push(arity);
4560                }
4561            }
4562        }
4563        let resolved = (|| {
4564            let required = arities
4565                .iter()
4566                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4567                .min()?;
4568            let total = arities.iter().map(|arity| arity.total()).max()?;
4569            let repeated = arities
4570                .iter()
4571                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4572            // Preprocessor conditions can leave more than one object-like parameter
4573            // bundle active in the target header's include closure. Preserve their
4574            // conservative callable envelope instead of choosing whichever definition
4575            // happened to be visited first.
4576            Some(CallableArity::new(required, total, repeated))
4577        })();
4578        self.callable_parameter_macro_arities
4579            .lock()
4580            .expect("C++ callable parameter-macro arity cache poisoned")
4581            .insert(cache_key, resolved);
4582        resolved
4583    }
4584
4585    pub fn include_activation_for_source(
4586        &self,
4587        cpp: &dyn CppSource,
4588        file: &ProjectFile,
4589        prepared: &PreparedSyntaxTree,
4590        donor_source: &ProjectFile,
4591    ) -> Option<usize> {
4592        let key = (file.clone(), donor_source.clone());
4593        if let Some(cached) = self
4594            .include_activation_cells
4595            .lock()
4596            .expect("C++ include activation cache poisoned")
4597            .get(&key)
4598            .copied()
4599        {
4600            return cached;
4601        }
4602        #[cfg(any(test, feature = "test-support"))]
4603        self.include_activation_build_count
4604            .fetch_add(1, Ordering::Relaxed);
4605        let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
4606        let mut cells = self
4607            .include_activation_cells
4608            .lock()
4609            .expect("C++ include activation cache poisoned");
4610        *cells.entry(key).or_insert(activation)
4611    }
4612
4613    pub fn conditional_include_projections_for_source(
4614        &self,
4615        file: &ProjectFile,
4616        prepared: &PreparedSyntaxTree,
4617        donor_source: &ProjectFile,
4618    ) -> Arc<[ConditionalIncludeProjection]> {
4619        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4620        let cell = self
4621            .conditional_include_projection_cells
4622            .lock()
4623            .expect("C++ conditional include projection cache poisoned")
4624            .entry(file.clone())
4625            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4626            .clone();
4627        let index = cell.get_or_build_pool_independent(|| {
4628            #[cfg(any(test, feature = "test-support"))]
4629            self.conditional_include_projection_index_build_count
4630                .fetch_add(1, Ordering::Relaxed);
4631            find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
4632                #[cfg(any(test, feature = "test-support"))]
4633                self.conditional_include_projection_state_count
4634                    .fetch_add(1, Ordering::Relaxed);
4635            })
4636        });
4637        index
4638            .get(donor_source)
4639            .cloned()
4640            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4641    }
4642
4643    #[cfg(any(test, feature = "test-support"))]
4644    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4645        (
4646            self.conditional_include_projection_index_build_count
4647                .load(Ordering::Relaxed),
4648            self.conditional_include_projection_state_count
4649                .load(Ordering::Relaxed),
4650        )
4651    }
4652
4653    #[cfg(any(test, feature = "test-support"))]
4654    pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4655        self.conditional_include_target_state_count
4656            .load(Ordering::Relaxed)
4657    }
4658
4659    #[cfg(any(test, feature = "test-support"))]
4660    pub fn include_activation_build_count_for_test(&self) -> usize {
4661        self.include_activation_build_count.load(Ordering::Relaxed)
4662    }
4663
4664    #[cfg(any(test, feature = "test-support"))]
4665    pub fn note_using_donor_activation_for_test(&self) {
4666        self.using_donor_activation_count
4667            .fetch_add(1, Ordering::Relaxed);
4668    }
4669
4670    #[cfg(not(any(test, feature = "test-support")))]
4671    pub fn note_using_donor_activation_for_test(&self) {}
4672
4673    #[cfg(any(test, feature = "test-support"))]
4674    pub fn note_using_namespace_lookup_for_test(&self) {
4675        self.using_namespace_lookup_count
4676            .fetch_add(1, Ordering::Relaxed);
4677    }
4678
4679    #[cfg(not(any(test, feature = "test-support")))]
4680    pub fn note_using_namespace_lookup_for_test(&self) {}
4681
4682    #[cfg(any(test, feature = "test-support"))]
4683    pub fn note_using_name_candidate_inspection_for_test(&self) {
4684        self.using_name_candidate_inspection_count
4685            .fetch_add(1, Ordering::Relaxed);
4686    }
4687
4688    #[cfg(not(any(test, feature = "test-support")))]
4689    pub fn note_using_name_candidate_inspection_for_test(&self) {}
4690
4691    #[cfg(any(test, feature = "test-support"))]
4692    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4693        (
4694            self.using_donor_activation_count.load(Ordering::Relaxed),
4695            self.using_namespace_lookup_count.load(Ordering::Relaxed),
4696            self.callable_reference_spec_build_count
4697                .load(Ordering::Relaxed),
4698            self.using_name_candidate_inspection_count
4699                .load(Ordering::Relaxed),
4700        )
4701    }
4702
4703    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4704        file == target.source()
4705            || self
4706                .visible_by_file
4707                .get(file)
4708                .is_some_and(|visible| visible.contains(target))
4709    }
4710
4711    /// Whether some declaration of `declaration`'s logical symbol is visible at
4712    /// `reference_byte` in `file`.
4713    ///
4714    /// The question is asked of the *logical* symbol, not of the physical unit:
4715    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
4716    /// and it does not have to be - what makes the call legal is the header
4717    /// declaration that the reference file does include. Reading that relation
4718    /// through `same_logical_callable` rather than through signature strings is
4719    /// the same #2010 correction the gates make, and it matters here because
4720    /// the body and the declaration are exactly the pair that spells one
4721    /// parameter type two ways.
4722    pub fn declaration_visible_at(
4723        &self,
4724        analyzer: &CppGraphSource<'_>,
4725        file: &ProjectFile,
4726        declaration: &CodeUnit,
4727        reference_byte: usize,
4728    ) -> bool {
4729        let reference_guards = OnceCell::new();
4730        self.visible_identifier_candidates(file, declaration.identifier())
4731            .filter(|candidate| {
4732                self.same_logical_callable(analyzer, candidate, declaration)
4733                    || flattened_macro_namespace_declaration_matches(
4734                        analyzer,
4735                        self.cpp,
4736                        file,
4737                        candidate,
4738                        declaration,
4739                        reference_byte,
4740                    )
4741            })
4742            .any(|candidate| {
4743                self.physical_declaration_visible_at(
4744                    analyzer,
4745                    file,
4746                    candidate,
4747                    reference_byte,
4748                    &reference_guards,
4749                )
4750            })
4751    }
4752
4753    /// Whether a physical declaration is visible at an exact structured
4754    /// reference node. Inverse C field references need the reference's own
4755    /// preprocessor environment before using the callable activation path:
4756    /// callable activation can establish that a field declaration is
4757    /// nameable, but it must not admit the opposite branch of that field's
4758    /// conditional family.
4759    pub fn declaration_visible_at_reference(
4760        &self,
4761        analyzer: &CppGraphSource<'_>,
4762        file: &ProjectFile,
4763        declaration: &CodeUnit,
4764        reference: Node<'_>,
4765    ) -> bool {
4766        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4767            return false;
4768        };
4769        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4770        let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4771        if !declaration_guards.iter().any(|(_, required)| {
4772            guards_compatible_at_reference(required, reference_guards.as_ref())
4773        }) {
4774            return false;
4775        }
4776        if declaration.source() == file
4777            && !analyzer.reference_uses_c_semantics(file)
4778            && (declaration.is_field() || declaration.is_callable())
4779            && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4780                self.indexed_enclosing_owner_scope(analyzer, file, reference)
4781                    .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4782            })
4783        {
4784            // C++ complete-class context makes members visible throughout the
4785            // class. The guard check above still excludes a declaration from
4786            // an incompatible preprocessor branch.
4787            return true;
4788        }
4789        if declaration.is_field() && declaration.source() == file {
4790            let reference_byte = reference.start_byte();
4791            let reference_function =
4792                real_function_definition_ancestor(reference, prepared.source());
4793            let guards = OnceCell::new();
4794            let field_reference = CallableReferenceContext {
4795                file,
4796                position: Some(CallableReferencePosition {
4797                    prepared: prepared.as_ref(),
4798                    byte: reference_byte,
4799                    guards: &guards,
4800                }),
4801            };
4802            let mut has_local_declaration = false;
4803            let mut local_declaration_visible = false;
4804            for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4805            {
4806                let Some(declaration_function) =
4807                    real_function_definition_ancestor(declaration, prepared.source())
4808                else {
4809                    continue;
4810                };
4811                has_local_declaration = true;
4812                if reference_function.is_some_and(|reference_function| {
4813                    reference_function.start_byte() == declaration_function.start_byte()
4814                        && reference_function.end_byte() == declaration_function.end_byte()
4815                }) && callable_preprocessor_context_is_visible_for_reference(
4816                    declaration,
4817                    prepared.source(),
4818                    &field_reference,
4819                ) && callable_declaration_activation_byte(declaration) < reference_byte
4820                {
4821                    local_declaration_visible = true;
4822                    break;
4823                }
4824            }
4825            if has_local_declaration {
4826                return local_declaration_visible;
4827            }
4828        }
4829        let guards = OnceCell::new();
4830        self.physical_declaration_visible_at(
4831            analyzer,
4832            file,
4833            declaration,
4834            reference.start_byte(),
4835            &guards,
4836        )
4837    }
4838
4839    /// C forward navigation may bind a call to a later same-file definition.
4840    /// There is no earlier source declaration to activate in that legacy C
4841    /// shape, but the call's preprocessor environment must still imply the
4842    /// definition's requirements. Ordinary C++ and inverse visibility retain
4843    /// the declaration-order rule in [`Self::declaration_visible_at`].
4844    pub fn declaration_visible_for_c_forward_call(
4845        &self,
4846        analyzer: &CppGraphSource<'_>,
4847        file: &ProjectFile,
4848        declaration: &CodeUnit,
4849        reference_byte: usize,
4850    ) -> bool {
4851        if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4852            return true;
4853        }
4854        if declaration.source() != file {
4855            return false;
4856        }
4857        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4858            return false;
4859        };
4860        let reference_guards = prepared
4861            .tree()
4862            .root_node()
4863            .descendant_for_byte_range(reference_byte, reference_byte)
4864            .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4865        declaration_guard_requirements(analyzer, self.cpp, declaration)
4866            .into_iter()
4867            .any(|(_, required)| {
4868                guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4869            })
4870    }
4871
4872    pub fn callable_arity_at_reference(
4873        &self,
4874        analyzer: &CppGraphSource<'_>,
4875        file: &ProjectFile,
4876        candidate: &CodeUnit,
4877        reference_byte: usize,
4878    ) -> Option<CallableArity> {
4879        let key = (file.clone(), logical_symbol_key(candidate));
4880        let cell = self
4881            .callable_reference_specs
4882            .lock()
4883            .expect("C++ callable reference-spec cache poisoned")
4884            .entry(key)
4885            .or_default()
4886            .clone();
4887        let spec = cell.get_or_init(|| {
4888            let prepared = self.cpp.prepared_syntax(self.token, file)?;
4889            let spec = TargetSpec::from_target(analyzer, candidate)?;
4890            let spec = spec
4891                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4892                .into_owned();
4893            #[cfg(any(test, feature = "test-support"))]
4894            self.callable_reference_spec_build_count
4895                .fetch_add(1, Ordering::Relaxed);
4896            Some(spec)
4897        });
4898        spec.as_ref()?.callable_arity_at(reference_byte)
4899    }
4900
4901    fn physical_declaration_visible_at(
4902        &self,
4903        analyzer: &CppGraphSource<'_>,
4904        file: &ProjectFile,
4905        declaration: &CodeUnit,
4906        reference_byte: usize,
4907        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4908    ) -> bool {
4909        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4910            return false;
4911        };
4912        let reference = CallableReferenceContext {
4913            file,
4914            position: Some(CallableReferencePosition {
4915                prepared: prepared.as_ref(),
4916                byte: reference_byte,
4917                guards: reference_guards,
4918            }),
4919        };
4920        if declaration.source() == file {
4921            return callable_declaration_activation_in_file(
4922                analyzer,
4923                prepared.as_ref(),
4924                declaration,
4925                &reference,
4926            )
4927            .or_else(|| {
4928                self.exhaustive_guard_family_activation(
4929                    analyzer,
4930                    prepared.as_ref(),
4931                    declaration,
4932                    &reference,
4933                )
4934            })
4935            .is_some_and(|activation| activation < reference_byte);
4936        }
4937        let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
4938            return false;
4939        };
4940        if self
4941            .foreign_callable_declaration_activation(
4942                analyzer,
4943                donor_syntax.as_ref(),
4944                declaration,
4945                &reference,
4946            )
4947            .or_else(|| {
4948                self.exhaustive_guard_family_activation(
4949                    analyzer,
4950                    donor_syntax.as_ref(),
4951                    declaration,
4952                    &reference,
4953                )
4954            })
4955            .is_none()
4956        {
4957            return false;
4958        }
4959        declaration_guard_requirements(analyzer, self.cpp, declaration)
4960            .into_iter()
4961            .any(|(_, declaration_guards)| {
4962                self.foreign_declaration_reachable_at_reference(
4963                    file,
4964                    prepared.as_ref(),
4965                    declaration.source(),
4966                    &declaration_guards,
4967                    reference.guards(),
4968                    reference_byte,
4969                )
4970            })
4971    }
4972
4973    /// The byte at which `declaration` activates inside the foreign file that
4974    /// `donor_syntax` describes.
4975    ///
4976    /// Which guard rule applies depends on what decides the reference's
4977    /// configuration. When `compile_commands.json` covers the reference's
4978    /// translation unit, the build is the proof source and the declaration's
4979    /// guards must follow from the reference's active guards plus the proven
4980    /// defines; a platform macro the build does not prove leaves the
4981    /// declaration invisible (#2011). With no compile coverage nothing decides
4982    /// a platform macro, so the cross-file rule stands: the donor resolves its
4983    /// own conditionals and only has to stay free of contradiction with the
4984    /// reference. Otherwise a libuv-shaped `#if defined(__linux__)` prototype
4985    /// in a shared header is hidden from every unguarded caller in another
4986    /// translation unit (#2988).
4987    fn foreign_callable_declaration_activation(
4988        &self,
4989        analyzer: &CppGraphSource<'_>,
4990        donor_syntax: &PreparedSyntaxTree,
4991        declaration: &CodeUnit,
4992        reference: &CallableReferenceContext<'_>,
4993    ) -> Option<usize> {
4994        let build_decides = !self.compile_context_is_absent(reference.file);
4995        let proven = self.compile_proven_guards(reference.file);
4996        let augmented;
4997        let active = match reference.guards() {
4998            Some(active) if !proven.is_empty() => {
4999                augmented = active.union(&proven).cloned().collect();
5000                Some(&augmented)
5001            }
5002            other => other,
5003        };
5004        nameable_callable_declaration_nodes(analyzer, donor_syntax, declaration)
5005            .into_iter()
5006            .filter(|node| {
5007                let Some(required) = callable_declaration_guard_requirements(
5008                    *node,
5009                    donor_syntax.source(),
5010                    reference,
5011                ) else {
5012                    return false;
5013                };
5014                if required.is_empty() {
5015                    return true;
5016                }
5017                if build_decides {
5018                    guard_requirements_hold_at_reference(&required, active)
5019                } else {
5020                    guards_compatible_at_reference(&required, reference.guards())
5021                }
5022            })
5023            .map(callable_declaration_activation_byte)
5024            .min()
5025    }
5026
5027    pub fn external_type_candidate_visible_at(
5028        &self,
5029        file: &ProjectFile,
5030        candidate: &CodeUnit,
5031        reference_byte: usize,
5032    ) -> bool {
5033        if candidate.source() == file {
5034            return true;
5035        }
5036        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5037            return false;
5038        };
5039        self.visible_identifier_candidates(file, candidate.identifier())
5040            .filter(|peer| same_logical_symbol(candidate, peer))
5041            .any(|peer| {
5042                peer.source() == file
5043                    || self
5044                        .include_activation_for_source(
5045                            self.cpp,
5046                            file,
5047                            prepared.as_ref(),
5048                            peer.source(),
5049                        )
5050                        .is_some_and(|activation| activation <= reference_byte)
5051            })
5052    }
5053
5054    pub fn external_type_declaration_visible_at(
5055        &self,
5056        file: &ProjectFile,
5057        candidate: &CodeUnit,
5058        reference_byte: usize,
5059    ) -> bool {
5060        if candidate.source() == file {
5061            return true;
5062        }
5063        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5064            return false;
5065        };
5066        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5067            .is_some_and(|activation| activation <= reference_byte)
5068    }
5069
5070    /// The preprocessor facts the build proves for a reference sited in
5071    /// `file` (#2011).
5072    ///
5073    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
5074    /// `Defined` fact, and a fact holds only when every compile configuration
5075    /// that governs the file agrees on it (intersection). The facts are
5076    /// strictly additive to the reference's active guard set: they can prove a
5077    /// required guard, but the guard check itself is never weakened and no
5078    /// implication is ever inferred from source text.
5079    ///
5080    /// A file with its own database entry answers from that entry alone
5081    /// (phase 1). A header takes its context from the translation units whose
5082    /// include closure reaches it, intersected across all of them (phase 2):
5083    /// the header is compiled once per including TU, so a fact holds for a
5084    /// header-sited reference only when every one of those compilations
5085    /// proves it. A reaching TU the database does not cover proves nothing,
5086    /// which empties the intersection. A file nothing covers or reaches has
5087    /// no facts and every check runs on source structure alone.
5088    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5089        if let Some(cached) = self
5090            .compile_proven_guard_cells
5091            .lock()
5092            .expect("C++ compile-proven guard cache poisoned")
5093            .get(file)
5094        {
5095            return Arc::clone(cached);
5096        }
5097        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5098            Some(names) => names,
5099            None => {
5100                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5101                let seed = translation_units.next().and_then(|translation_unit| {
5102                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5103                });
5104                match seed {
5105                    None => HashSet::default(),
5106                    Some(mut names) => {
5107                        for translation_unit in translation_units {
5108                            let Some(reached) = context_fact_names(
5109                                self.cpp.compile_contexts_for(&translation_unit),
5110                            ) else {
5111                                names.clear();
5112                                break;
5113                            };
5114                            names.retain(|name| reached.contains(name));
5115                            if names.is_empty() {
5116                                break;
5117                            }
5118                        }
5119                        names
5120                    }
5121                }
5122            }
5123        };
5124        let proven = Arc::new(
5125            names
5126                .into_iter()
5127                .map(PreprocessorGuard::Defined)
5128                .collect::<HashSet<_>>(),
5129        );
5130        self.compile_proven_guard_cells
5131            .lock()
5132            .expect("C++ compile-proven guard cache poisoned")
5133            .insert(file.clone(), Arc::clone(&proven));
5134        proven
5135    }
5136
5137    /// The rule a conditional `#include` path's guards face on the way to a
5138    /// reference in `file`.
5139    ///
5140    /// This is the include-edge analogue of the choice
5141    /// [`Self::foreign_callable_declaration_activation`] makes for a foreign
5142    /// declaration's own guards (#2988), and it is decided by the same thing:
5143    /// what settles the reference's configuration. Memoized per file because
5144    /// the answer depends on nothing else, and the callers ask it once per
5145    /// candidate.
5146    fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5147        if let Some(cached) = self
5148            .include_path_admission_cells
5149            .lock()
5150            .expect("C++ include-path admission cache poisoned")
5151            .get(file)
5152            .copied()
5153        {
5154            return cached;
5155        }
5156        let admission = if self.compile_context_is_absent(file) {
5157            IncludePathAdmission::Compatible
5158        } else {
5159            IncludePathAdmission::Proven
5160        };
5161        self.include_path_admission_cells
5162            .lock()
5163            .expect("C++ include-path admission cache poisoned")
5164            .insert(file.clone(), admission);
5165        admission
5166    }
5167
5168    /// Whether no compile data covers the compilations of `file`: it has no
5169    /// database entry of its own, and either nothing reaches it or some
5170    /// translation unit that reaches it has no entry. This is the state a
5171    /// regenerated `compile_commands.json` could decide; data that is present
5172    /// for every governing compilation but does not prove a guard is a
5173    /// decided conservative miss, not this state.
5174    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5175        if !self.cpp.compile_contexts_for(file).is_empty() {
5176            return false;
5177        }
5178        let translation_units = self.cpp.reaching_translation_units(file);
5179        translation_units.is_empty()
5180            || translation_units
5181                .iter()
5182                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5183    }
5184
5185    /// Whether a lookup miss for `identifier` in `file` is explainable by
5186    /// missing compile context (#2011): some same-name declaration is
5187    /// reachable through a conditional include whose required guards neither
5188    /// contradict the reference's active guards nor follow from them, and the
5189    /// translation unit has no compile-commands entry that could decide the
5190    /// question. Callers surface this as an explicit "requires compile
5191    /// context" incompleteness instead of an indistinguishable miss.
5192    ///
5193    /// A structurally disproven declaration (contradicting guards) and a TU
5194    /// whose compile context exists but does not prove the guard both answer
5195    /// `false`: those misses are decided, not incomplete.
5196    pub fn miss_requires_compile_context(
5197        &self,
5198        file: &ProjectFile,
5199        identifier: &str,
5200        reference: Node<'_>,
5201    ) -> bool {
5202        if !self.compile_context_is_absent(file) {
5203            return false;
5204        }
5205        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5206            return false;
5207        };
5208        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5209        let reference_byte = reference.start_byte();
5210        let mut sources = self
5211            .visible_identifier_candidates(file, identifier)
5212            .map(CodeUnit::source)
5213            .filter(|source| *source != file)
5214            .collect::<Vec<_>>();
5215        sources.sort();
5216        sources.dedup();
5217        sources.into_iter().any(|declaration_source| {
5218            self.conditional_include_projections_for_source(
5219                file,
5220                prepared.as_ref(),
5221                declaration_source,
5222            )
5223            .iter()
5224            .any(|projection| {
5225                projection.activation_byte <= reference_byte
5226                    && !guard_requirements_hold_at_reference(
5227                        &projection.required_guards,
5228                        reference_guards.as_ref(),
5229                    )
5230                    && guards_compatible_at_reference(
5231                        &projection.required_guards,
5232                        reference_guards.as_ref(),
5233                    )
5234            })
5235        })
5236    }
5237
5238    /// Decide whether a declaration that lives in another file reaches a
5239    /// reference in `file`.
5240    ///
5241    /// An external header selects its declaration branch before the reference
5242    /// file is parsed. Require compatible reference guards, but do not test
5243    /// the header's guard expression for stability in the reference file: a
5244    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
5245    /// wraps every declaration of a portable C header, and demanding it would
5246    /// hide the whole header. Guards that the reference file imposes on its
5247    /// own `#include` still have to be stable, and must satisfy the rule
5248    /// [`IncludePathAdmission`] names for this reference's translation unit.
5249    fn foreign_declaration_reachable_at_reference(
5250        &self,
5251        file: &ProjectFile,
5252        prepared: &PreparedSyntaxTree,
5253        declaration_source: &ProjectFile,
5254        declaration_guards: &HashSet<PreprocessorGuard>,
5255        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5256        reference_byte: usize,
5257    ) -> bool {
5258        // The translation unit's build-proven defines join the reference's
5259        // active guard set (#2011): a conditional include like the nng
5260        // `NNG_PLATFORM_POSIX` chain is provable only by the compile command.
5261        // A reference whose own environment is unknown stays unknown -- the
5262        // facts extend an environment, they never invent one.
5263        let proven = self.compile_proven_guards(file);
5264        let augmented;
5265        let reference_guards = match reference_guards {
5266            Some(active) if !proven.is_empty() => {
5267                augmented = active.union(&proven).cloned().collect();
5268                Some(&augmented)
5269            }
5270            other => other,
5271        };
5272        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5273            eprintln!(
5274                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5275                declaration_source.rel_path().display(),
5276            );
5277        }
5278        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5279            return false;
5280        }
5281        if self
5282            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5283            .is_some_and(|activation| activation <= reference_byte)
5284        {
5285            return true;
5286        }
5287        let projections =
5288            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5289        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5290            eprintln!(
5291                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5292                declaration_source.rel_path().display(),
5293                declaration_guards.len(),
5294                proven.len(),
5295                projections.len(),
5296            );
5297        }
5298        let admission = self.include_path_admission(file);
5299        projections.iter().any(|projection| {
5300            projection.activation_byte <= reference_byte
5301                && admission.admits(
5302                    &projection.required_guards,
5303                    &projection.partial_guards,
5304                    reference_guards,
5305                )
5306                && self.preprocessor_guards_stable_between(
5307                    file,
5308                    projection.activation_byte,
5309                    reference_byte,
5310                    &projection.required_guards,
5311                )
5312        })
5313    }
5314
5315    fn foreign_declaration_may_be_reachable_from_raw_guards(
5316        &self,
5317        file: &ProjectFile,
5318        prepared: &PreparedSyntaxTree,
5319        declaration_source: &ProjectFile,
5320        declaration_guards: &HashSet<PreprocessorGuard>,
5321        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5322        reference_byte: usize,
5323    ) -> bool {
5324        let proven = self.compile_proven_guards(file);
5325        let augmented;
5326        let reference_guards = match reference_guards {
5327            Some(active) if !proven.is_empty() => {
5328                augmented = active.union(&proven).cloned().collect();
5329                Some(&augmented)
5330            }
5331            other => other,
5332        };
5333        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5334            return false;
5335        }
5336        if self
5337            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5338            .is_some_and(|activation| activation <= reference_byte)
5339        {
5340            return true;
5341        }
5342        let reachable = find_conditional_include_projection_for_source(
5343            self.cpp,
5344            self.token,
5345            file,
5346            prepared,
5347            declaration_source,
5348            self.include_path_admission(file),
5349            reference_guards,
5350            reference_byte,
5351            &|| {
5352                #[cfg(any(test, feature = "test-support"))]
5353                self.conditional_include_target_state_count
5354                    .fetch_add(1, Ordering::Relaxed);
5355            },
5356        );
5357        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5358            eprintln!(
5359                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5360                declaration_source.rel_path().display(),
5361                declaration_guards.len(),
5362                proven.len(),
5363                reference_guards.map_or(0, HashSet::len),
5364            );
5365        }
5366        reachable
5367    }
5368
5369    fn foreign_declaration_reachable_from_compile_proven_guards(
5370        &self,
5371        file: &ProjectFile,
5372        prepared: &PreparedSyntaxTree,
5373        declaration_source: &ProjectFile,
5374        declaration_guards: &HashSet<PreprocessorGuard>,
5375        reference_byte: usize,
5376    ) -> bool {
5377        let proven = self.compile_proven_guards(file);
5378        if proven.is_empty()
5379            || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5380        {
5381            return false;
5382        }
5383        let projections =
5384            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5385        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5386            eprintln!(
5387                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5388                declaration_source.rel_path().display(),
5389                declaration_guards.len(),
5390                proven.len(),
5391                projections.len(),
5392            );
5393        }
5394        projections.iter().any(|projection| {
5395            projection.activation_byte <= reference_byte
5396                    && guard_requirements_hold_at_reference(
5397                        &projection.required_guards,
5398                        Some(proven.as_ref()),
5399                    )
5400                    // Build facts hold at translation-unit entry. A source
5401                    // `#undef` or an earlier include may invalidate one before
5402                    // this conditional include is reached; mutations after the
5403                    // include cannot revoke declarations it already supplied.
5404                    && self.preprocessor_guards_stable_between(
5405                        file,
5406                        0,
5407                        projection.activation_byte,
5408                        &projection.required_guards,
5409                    )
5410        })
5411    }
5412
5413    pub fn external_type_candidate_visible_in_context(
5414        &self,
5415        analyzer: &CppGraphSource<'_>,
5416        file: &ProjectFile,
5417        candidate: &CodeUnit,
5418        reference: Node<'_>,
5419    ) -> bool {
5420        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5421        if report_stats {
5422            eprintln!(
5423                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5424                candidate.fq_name(),
5425                candidate.source().rel_path().display(),
5426                file.rel_path().display(),
5427                reference.start_byte(),
5428            );
5429        }
5430        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5431            return false;
5432        };
5433        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5434        let reference_guards = OnceCell::new();
5435        let reference_guards_at_site = || {
5436            reference_guards.get_or_init(|| {
5437                let started = Instant::now();
5438                if report_stats {
5439                    eprintln!(
5440                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5441                        file.rel_path().display(),
5442                        reference.start_byte(),
5443                        raw_reference_guards.as_ref().map_or(0, HashSet::len),
5444                    );
5445                }
5446                let macro_environment = self.macro_environment(file, reference.start_byte());
5447                let filtered = raw_reference_guards
5448                    .clone()
5449                    .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5450                if report_stats {
5451                    eprintln!(
5452                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5453                        filtered.is_some(),
5454                        started.elapsed().as_millis(),
5455                    );
5456                }
5457                filtered
5458            })
5459        };
5460
5461        let peers = self
5462            .visible_identifier_candidates(file, candidate.identifier())
5463            .filter(|peer| same_logical_symbol(candidate, peer))
5464            .collect::<Vec<_>>();
5465        if report_stats {
5466            let peer_sources = peers
5467                .iter()
5468                .map(|peer| peer.source().rel_path().display().to_string())
5469                .collect::<Vec<_>>();
5470            eprintln!(
5471                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5472                candidate.fq_name(),
5473            );
5474        }
5475        let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5476            declaration_guard_requirements(analyzer, self.cpp, peer)
5477                .into_iter()
5478                .any(|(declaration_byte, declaration_guards)| {
5479                    if peer.source() == file {
5480                        let visible = declaration_byte < reference.start_byte()
5481                            && declaration_guards.is_empty();
5482                        if report_stats {
5483                            eprintln!(
5484                                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5485                                peer.source().rel_path().display(),
5486                                declaration_guards.len(),
5487                            );
5488                        }
5489                        return visible;
5490                    }
5491                    let direct = declaration_guards.is_empty()
5492                        && self
5493                            .include_activation_for_source(
5494                                self.cpp,
5495                                file,
5496                                prepared.as_ref(),
5497                                peer.source(),
5498                            )
5499                            .is_some_and(|activation| activation <= reference.start_byte());
5500                    let compile_proven = !direct
5501                        && self.foreign_declaration_reachable_from_compile_proven_guards(
5502                            file,
5503                            prepared.as_ref(),
5504                            peer.source(),
5505                            &declaration_guards,
5506                            reference.start_byte(),
5507                        );
5508                    if report_stats {
5509                        eprintln!(
5510                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5511                            peer.source().rel_path().display(),
5512                            declaration_guards.len(),
5513                        );
5514                    }
5515                    direct || compile_proven
5516                })
5517        });
5518        if directly_visible_without_reference_environment {
5519            if report_stats {
5520                eprintln!(
5521                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5522                    candidate.fq_name(),
5523                );
5524            }
5525            return true;
5526        }
5527        let directly_visible = peers.iter().any(|peer| {
5528            declaration_guard_requirements(analyzer, self.cpp, peer)
5529                .into_iter()
5530                .any(|(declaration_byte, declaration_guards)| {
5531                    if peer.source() == file {
5532                        if declaration_byte >= reference.start_byte() {
5533                            return false;
5534                        }
5535                        if !guard_requirements_hold_at_reference(
5536                            &declaration_guards,
5537                            raw_reference_guards.as_ref(),
5538                        ) {
5539                            return false;
5540                        }
5541                        return guard_requirements_hold_at_reference(
5542                            &declaration_guards,
5543                            reference_guards_at_site().as_ref(),
5544                        ) && self.preprocessor_guards_stable_between(
5545                            file,
5546                            declaration_byte,
5547                            reference.start_byte(),
5548                            &declaration_guards,
5549                        );
5550                    }
5551                    let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5552                        file,
5553                        prepared.as_ref(),
5554                        peer.source(),
5555                        &declaration_guards,
5556                        raw_reference_guards.as_ref(),
5557                        reference.start_byte(),
5558                    );
5559                    if report_stats {
5560                        eprintln!(
5561                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5562                            peer.source().rel_path().display(),
5563                            declaration_guards.len(),
5564                        );
5565                    }
5566                    if !raw_feasible {
5567                        return false;
5568                    }
5569                    self.foreign_declaration_reachable_at_reference(
5570                        file,
5571                        prepared.as_ref(),
5572                        peer.source(),
5573                        &declaration_guards,
5574                        reference_guards_at_site().as_ref(),
5575                        reference.start_byte(),
5576                    )
5577                })
5578        });
5579        if directly_visible {
5580            if report_stats {
5581                eprintln!(
5582                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5583                    candidate.fq_name(),
5584                );
5585            }
5586            return true;
5587        }
5588        let complementary = self
5589            .visible_identifier_candidates(file, candidate.identifier())
5590            .filter(|peer| {
5591                peer.kind() == candidate.kind()
5592                    && peer.fq_name() == candidate.fq_name()
5593                    && peer.source() == candidate.source()
5594            })
5595            .collect::<Vec<_>>();
5596        // A completed #if/#else family declares the shared source-level name
5597        // before this reference. A later macro mutation cannot revoke that
5598        // declaration. The family gate below rejects declarations split across
5599        // separate conditional blocks, where mutation can change coverage.
5600        let complementary_family =
5601            self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5602        let raw_candidate_branch_compatible = complementary_family
5603            && raw_reference_guards.as_ref().is_some_and(|active| {
5604                declaration_guard_requirements(analyzer, self.cpp, candidate)
5605                    .iter()
5606                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5607            });
5608        if report_stats {
5609            eprintln!(
5610                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5611                candidate.fq_name(),
5612                complementary.len(),
5613                complementary_family,
5614                raw_candidate_branch_compatible,
5615            );
5616        }
5617        let candidate_branch_compatible = raw_candidate_branch_compatible
5618            && reference_guards_at_site().as_ref().is_some_and(|active| {
5619                declaration_guard_requirements(analyzer, self.cpp, candidate)
5620                    .iter()
5621                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5622            });
5623        let complementary_visible = candidate_branch_compatible
5624            && if candidate.source() == file {
5625                declaration_guard_requirements(analyzer, self.cpp, candidate)
5626                    .iter()
5627                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5628            } else {
5629                self.include_activation_for_source(
5630                    self.cpp,
5631                    file,
5632                    prepared.as_ref(),
5633                    candidate.source(),
5634                )
5635                .is_some_and(|activation| activation <= reference.start_byte())
5636            };
5637        if report_stats {
5638            eprintln!(
5639                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5640                if complementary_visible {
5641                    "complementary"
5642                } else {
5643                    "missing"
5644                },
5645                candidate.fq_name(),
5646            );
5647        }
5648        complementary_visible
5649    }
5650
5651    pub fn is_exhaustive_same_fqn_type_declaration_family(
5652        &self,
5653        analyzer: &CppGraphSource<'_>,
5654        file: &ProjectFile,
5655        candidate: &CodeUnit,
5656    ) -> bool {
5657        let candidates = self
5658            .visible_identifier_candidates(file, candidate.identifier())
5659            .filter(|peer| {
5660                peer.kind() == candidate.kind()
5661                    && peer.fq_name() == candidate.fq_name()
5662                    && peer.source() == candidate.source()
5663            })
5664            .collect::<Vec<_>>();
5665        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5666    }
5667
5668    /// Prove a nested type alias used as a dependent member-pointer owner when
5669    /// its owning class has mutually-exclusive declarations.  A common C++11
5670    /// compatibility shape provides the owning class in one preprocessor
5671    /// branch and aliases it to a standard-library type in the other branch;
5672    /// the nested fallback alias is therefore not itself active in every
5673    /// branch even though the qualified owner API is.
5674    ///
5675    /// This is deliberately narrower than ordinary type visibility.  The
5676    /// caller has already recovered a member-pointer owner path from the CST;
5677    /// this helper additionally requires the target's structured parent to
5678    /// match that path, physical source visibility, and exact preprocessor
5679    /// guard agreement with the parent declaration.  Only then may the
5680    /// parent's direct/complementary same-FQN visibility stand in for the
5681    /// nested terminal's active-branch check.
5682    pub fn dependent_member_pointer_alias_visible_in_context(
5683        &self,
5684        analyzer: &CppGraphSource<'_>,
5685        file: &ProjectFile,
5686        candidate: &CodeUnit,
5687        owner_components: &[String],
5688        reference: Node<'_>,
5689    ) -> bool {
5690        if !analyzer
5691            .type_alias_provider()
5692            .is_some_and(|provider| provider.is_type_alias(candidate))
5693        {
5694            return false;
5695        }
5696        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5697            return false;
5698        };
5699        if terminal != candidate.identifier()
5700            || canonical_cpp_scope_components(candidate) != owner_components
5701        {
5702            return false;
5703        }
5704        let Some(expected_parent_fq_name) =
5705            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5706        else {
5707            return false;
5708        };
5709        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5710            return false;
5711        };
5712        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5713            || parent_anchor.source() != candidate.source()
5714            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5715        {
5716            return false;
5717        }
5718
5719        // The ordinary path already handles unguarded aliases (and preserves
5720        // same-file declaration ordering).  This fallback is only for a
5721        // physically visible declaration whose guard is the owning branch's
5722        // guard, so reject a same-file declaration that appears after the
5723        // reference before considering guard compatibility.
5724        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5725            || candidate.source() == file
5726                && !analyzer
5727                    .ranges(candidate)
5728                    .iter()
5729                    .any(|range| range.start_byte < reference.start_byte())
5730        {
5731            return false;
5732        }
5733
5734        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5735        if candidate_guards.is_empty() {
5736            return false;
5737        }
5738        let same_guard_sets =
5739            |left: &[(usize, HashSet<PreprocessorGuard>)],
5740             right: &[(usize, HashSet<PreprocessorGuard>)]| {
5741                left.iter().all(|(_, left_guards)| {
5742                    right
5743                        .iter()
5744                        .any(|(_, right_guards)| left_guards == right_guards)
5745                })
5746            };
5747        let parent_candidates = self
5748            .visible_identifier_candidates(file, parent_anchor.identifier())
5749            .filter(|peer| {
5750                peer.kind() == parent_anchor.kind()
5751                    && peer.fq_name() == expected_parent_fq_name.as_str()
5752                    && peer.source() == parent_anchor.source()
5753                    && canonical_cpp_scope_components(peer) == owner_prefix
5754            })
5755            .filter_map(|peer| {
5756                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5757                (candidate_guards.len() == parent_guards.len()
5758                    && same_guard_sets(&candidate_guards, &parent_guards)
5759                    && same_guard_sets(&parent_guards, &candidate_guards))
5760                .then(|| (peer.clone(), parent_guards))
5761            })
5762            .collect::<Vec<_>>();
5763        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5764            return false;
5765        };
5766
5767        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5768            return false;
5769        };
5770        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5771        else {
5772            return false;
5773        };
5774        // An external header selects its declaration branch before the
5775        // reference file is parsed. Require compatible reference guards, but
5776        // do not test the header's guard expression for stability in the
5777        // reference file. Same-file aliases still require that stability.
5778        if !candidate_guards.iter().any(|(_, target_guards)| {
5779            guards_compatible_at_reference(target_guards, Some(&reference_guards))
5780                && (candidate.source() != file
5781                    || self.preprocessor_guards_stable_between(
5782                        file,
5783                        0,
5784                        reference.start_byte(),
5785                        target_guards,
5786                    ))
5787        }) {
5788            return false;
5789        }
5790
5791        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5792    }
5793
5794    /// Check a type candidate's preprocessor/import context without imposing
5795    /// ordinary declaration-before-reference ordering for same-file peers.
5796    ///
5797    /// C++ class scope makes member names visible throughout the complete
5798    /// class, including a trailing return type that appears before the member
5799    /// alias declaration in source order. Callers must first prove that the
5800    /// reference is inside the candidate's indexed class owner; this helper
5801    /// only relaxes the byte-order predicate while retaining guard and include
5802    /// activation checks.
5803    pub fn external_type_candidate_guard_compatible_in_context(
5804        &self,
5805        analyzer: &CppGraphSource<'_>,
5806        file: &ProjectFile,
5807        candidate: &CodeUnit,
5808        reference: Node<'_>,
5809    ) -> bool {
5810        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5811            return false;
5812        };
5813        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5814
5815        self.visible_identifier_candidates(file, candidate.identifier())
5816            .filter(|peer| same_logical_symbol(candidate, peer))
5817            .any(|peer| {
5818                declaration_guard_requirements(analyzer, self.cpp, peer)
5819                    .into_iter()
5820                    .any(|(declaration_byte, declaration_guards)| {
5821                        if peer.source() == file {
5822                            let (start, end) = if declaration_byte <= reference.start_byte() {
5823                                (declaration_byte, reference.start_byte())
5824                            } else {
5825                                (reference.start_byte(), declaration_byte)
5826                            };
5827                            return guard_requirements_hold_at_reference(
5828                                &declaration_guards,
5829                                reference_guards.as_ref(),
5830                            ) && self.preprocessor_guards_stable_between(
5831                                file,
5832                                start,
5833                                end,
5834                                &declaration_guards,
5835                            );
5836                        }
5837                        self.foreign_declaration_reachable_at_reference(
5838                            file,
5839                            prepared.as_ref(),
5840                            peer.source(),
5841                            &declaration_guards,
5842                            reference_guards.as_ref(),
5843                            reference.start_byte(),
5844                        )
5845                    })
5846            })
5847    }
5848
5849    /// Whether a same-file callable declaration is nameable from `reference`
5850    /// after deliberately relaxing declaration-before-reference ordering.
5851    ///
5852    /// Ordinary lookup still requires an earlier declaration. Definition
5853    /// navigation for incomplete C translation units may recover a later
5854    /// definition, but only when it is at file scope and its preprocessor
5855    /// requirements hold at the call (#2404).
5856    pub fn same_file_callable_guard_compatible_ignoring_order(
5857        &self,
5858        analyzer: &CppGraphSource<'_>,
5859        file: &ProjectFile,
5860        candidate: &CodeUnit,
5861        reference: Node<'_>,
5862    ) -> bool {
5863        if candidate.source() != file || !candidate.is_callable() {
5864            return false;
5865        }
5866        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5867            return false;
5868        };
5869        let guards = OnceCell::new();
5870        let context = CallableReferenceContext {
5871            file,
5872            position: Some(CallableReferencePosition {
5873                prepared: prepared.as_ref(),
5874                byte: reference.start_byte(),
5875                guards: &guards,
5876            }),
5877        };
5878        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
5879            .into_iter()
5880            .any(|declaration| {
5881                callable_preprocessor_context_is_visible_for_reference(
5882                    declaration,
5883                    prepared.source(),
5884                    &context,
5885                )
5886            })
5887    }
5888
5889    pub fn type_candidate_may_be_visible_before_reference(
5890        &self,
5891        analyzer: &CppGraphSource<'_>,
5892        file: &ProjectFile,
5893        candidate: &CodeUnit,
5894        reference_byte: usize,
5895    ) -> bool {
5896        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5897            return false;
5898        };
5899        let root = prepared.tree().root_node();
5900        let end_byte = reference_byte
5901            .saturating_add(1)
5902            .min(prepared.source().len());
5903        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5904            return false;
5905        };
5906        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5907    }
5908
5909    pub fn preprocessor_guards_stable_between(
5910        &self,
5911        file: &ProjectFile,
5912        start_byte: usize,
5913        end_byte: usize,
5914        guards: &HashSet<PreprocessorGuard>,
5915    ) -> bool {
5916        if guards.is_empty() || start_byte >= end_byte {
5917            return true;
5918        }
5919        let cell = self.macro_event_cell(file);
5920        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5921        let mut visited = HashSet::from_iter([file.clone()]);
5922        !events.iter().any(|event| {
5923            event.byte() >= start_byte
5924                && event.byte() < end_byte
5925                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5926        })
5927    }
5928
5929    fn macro_event_may_mutate_guards(
5930        &self,
5931        event: &MacroEvent,
5932        guards: &HashSet<PreprocessorGuard>,
5933        visited: &mut HashSet<ProjectFile>,
5934    ) -> bool {
5935        match event {
5936            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5937                guards.iter().any(|guard| guard.may_depend_on_macro(name))
5938            }
5939            MacroEvent::Include { targets, .. } => {
5940                targets.is_empty()
5941                    || targets
5942                        .iter()
5943                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
5944            }
5945            MacroEvent::Invalidate { .. } => true,
5946        }
5947    }
5948
5949    fn source_may_mutate_guards(
5950        &self,
5951        file: &ProjectFile,
5952        guards: &HashSet<PreprocessorGuard>,
5953        visited: &mut HashSet<ProjectFile>,
5954    ) -> bool {
5955        if !visited.insert(file.clone()) {
5956            return false;
5957        }
5958        let cell = self.macro_event_cell(file);
5959        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5960        events
5961            .iter()
5962            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5963    }
5964
5965    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5966        let normalized = normalize_reference_name(raw_name)?;
5967        self.type_candidates(file, &normalized)
5968            .into_iter()
5969            .next()
5970            .cloned()
5971    }
5972
5973    /// Mirror forward navigation's visible-name fallback for a bare parameter
5974    /// type after lexical owner and inheritance lookup is exhausted.
5975    ///
5976    /// Generated or otherwise unindexed base classes can hide the alias that
5977    /// makes a parameter type valid C++. Accept the fallback only when every
5978    /// include-visible class or alias with that spelling canonicalizes to one
5979    /// logical type. A shadowing local type resolves lexically before this
5980    /// path, while distinct visible types keep the result ambiguous.
5981    pub fn unique_visible_parameter_type_fallback(
5982        &self,
5983        analyzer: &CppGraphSource<'_>,
5984        file: &ProjectFile,
5985        node: Node<'_>,
5986        source: &str,
5987    ) -> Option<CodeUnit> {
5988        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5989            return None;
5990        }
5991        let name = node_text(node, source);
5992        let candidates = self
5993            .visible_identifier_candidates(file, name)
5994            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
5995            .filter(|candidate| {
5996                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
5997            })
5998            .collect::<Vec<_>>();
5999        self.unique_canonical_type_candidate(analyzer, file, &candidates)
6000    }
6001
6002    pub fn resolve_type_node_result(
6003        &self,
6004        file: &ProjectFile,
6005        node: Node<'_>,
6006        source: &str,
6007    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6008        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6009            return Ok(None);
6010        };
6011        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6012            return Ok(Some(primary));
6013        };
6014        self.resolve_template_arguments(file, primary, &arguments)
6015            .map(Some)
6016    }
6017
6018    pub fn resolve_type_node_primary(
6019        &self,
6020        file: &ProjectFile,
6021        node: Node<'_>,
6022        source: &str,
6023    ) -> Option<CodeUnit> {
6024        let components = cpp_type_name_components(node, source)?;
6025        self.resolve_type(file, &components.join("::"))
6026    }
6027
6028    pub fn resolve_template_arguments(
6029        &self,
6030        file: &ProjectFile,
6031        primary: CodeUnit,
6032        arguments: &[CppTemplateExpression],
6033    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6034        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6035    }
6036
6037    fn resolve_template_arguments_inner(
6038        &self,
6039        file: &ProjectFile,
6040        primary: CodeUnit,
6041        arguments: &[CppTemplateExpression],
6042        seen_aliases: &mut HashSet<CodeUnit>,
6043    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6044        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6045            && let Some(alias_target) = &metadata.alias_target
6046        {
6047            if !seen_aliases.insert(primary.clone()) {
6048                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6049            }
6050            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6051                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6052            let target_name = alias_target.components.join("::");
6053            let target_primary = if alias_target.global {
6054                unique_logical_type_candidate(self.type_candidates(file, &target_name))
6055            } else {
6056                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6057            };
6058            let Some(target_primary) = target_primary else {
6059                // A dependent or external RHS cannot be canonicalized from the
6060                // indexed graph. Preserve the alias's direct identity instead
6061                // of inventing a target from its source spelling.
6062                return Ok(primary);
6063            };
6064            let Some(target_arguments) = &alias_target.arguments else {
6065                return Ok(target_primary);
6066            };
6067            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6068                .ok_or(CppTemplateResolutionError::Substitution)?;
6069            return self.resolve_template_arguments_inner(
6070                file,
6071                target_primary,
6072                &target_arguments,
6073                seen_aliases,
6074            );
6075        }
6076
6077        let primary_fq_name = self
6078            .cpp_template_metadata
6079            .get(&primary)
6080            .map(|metadata| metadata.primary_fq_name.clone())
6081            .unwrap_or_else(|| primary.fq_name());
6082        let has_specialization_metadata = self
6083            .cpp_template_families
6084            .get(&primary_fq_name)
6085            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6086        if !has_specialization_metadata {
6087            return Ok(primary);
6088        }
6089        self.select_template_specialization(file, &primary, arguments)
6090    }
6091
6092    fn select_template_specialization(
6093        &self,
6094        file: &ProjectFile,
6095        resolved: &CodeUnit,
6096        explicit_arguments: &[CppTemplateExpression],
6097    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6098        let primary_fq_name = self
6099            .cpp_template_metadata
6100            .get(resolved)
6101            .map(|metadata| metadata.primary_fq_name.clone())
6102            .unwrap_or_else(|| resolved.fq_name());
6103        let family = self
6104            .cpp_template_families
6105            .get(&primary_fq_name)
6106            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6107        let primary_candidates = family
6108            .iter()
6109            .filter_map(|unit| {
6110                let metadata = self.cpp_template_metadata.get(unit)?;
6111                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6112            })
6113            .collect::<Vec<_>>();
6114        let primary_unit = primary_candidates
6115            .iter()
6116            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6117            .or_else(|| {
6118                primary_candidates
6119                    .iter()
6120                    .map(|(unit, _)| *unit)
6121                    .min_by_key(|unit| {
6122                        (
6123                            unit.source().to_string(),
6124                            unit.signature().unwrap_or_default(),
6125                        )
6126                    })
6127            })
6128            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6129        let primary_parameters =
6130            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6131                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6132        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6133            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6134
6135        let mut applicable = Vec::new();
6136        for unit in family {
6137            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6138                continue;
6139            };
6140            if metadata.is_primary() || !self.is_visible(file, unit) {
6141                continue;
6142            }
6143            if !cpp_specialization_matches(metadata, &expanded) {
6144                continue;
6145            }
6146            applicable.push((unit, metadata));
6147        }
6148        if applicable.is_empty() {
6149            return Ok(primary_unit.clone());
6150        }
6151
6152        // A scalar constraint count cannot represent C++ partial ordering:
6153        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
6154        // Select only a logical candidate whose structural pattern is strictly
6155        // more specialized than every other distinct applicable candidate.
6156        let winners = applicable
6157            .iter()
6158            .filter(|(candidate, candidate_metadata)| {
6159                applicable.iter().all(|(other, other_metadata)| {
6160                    same_visible_symbol(candidate, other)
6161                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6162                })
6163            })
6164            .copied()
6165            .collect::<Vec<_>>();
6166        let Some((selected, _)) = winners.first() else {
6167            // Mutually incomparable applicable candidates: every one of them
6168            // is a live contender.
6169            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6170                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6171            });
6172        };
6173        if winners
6174            .iter()
6175            .any(|(unit, _)| !same_visible_symbol(unit, selected))
6176        {
6177            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6178                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6179            });
6180        }
6181        Ok((*selected).clone())
6182    }
6183
6184    pub fn resolve_type_components_lexically(
6185        &self,
6186        analyzer: &CppGraphSource<'_>,
6187        file: &ProjectFile,
6188        components: &[String],
6189        global: bool,
6190        lexical_scope: &[String],
6191    ) -> LexicalTypeResolution {
6192        self.resolve_type_components_lexically_inner(
6193            analyzer,
6194            file,
6195            components,
6196            global,
6197            lexical_scope,
6198            TypeCandidateResolution::Canonical,
6199        )
6200    }
6201
6202    pub fn resolve_type_components_lexically_for_forward(
6203        &self,
6204        analyzer: &CppGraphSource<'_>,
6205        file: &ProjectFile,
6206        components: &[String],
6207        global: bool,
6208        lexical_scope: &[String],
6209    ) -> LexicalTypeResolution {
6210        self.resolve_type_components_lexically_inner(
6211            analyzer,
6212            file,
6213            components,
6214            global,
6215            lexical_scope,
6216            TypeCandidateResolution::PreserveAlias,
6217        )
6218    }
6219
6220    pub fn resolve_type_components_lexically_for_target(
6221        &self,
6222        analyzer: &CppGraphSource<'_>,
6223        file: &ProjectFile,
6224        components: &[String],
6225        global: bool,
6226        lexical_scope: &[String],
6227        target: &CodeUnit,
6228    ) -> LexicalTypeResolution {
6229        #[cfg(any(test, feature = "test-support"))]
6230        self.target_preserving_type_resolution_count
6231            .fetch_add(1, Ordering::Relaxed);
6232        self.resolve_type_components_lexically_inner(
6233            analyzer,
6234            file,
6235            components,
6236            global,
6237            lexical_scope,
6238            TypeCandidateResolution::PreserveTarget(target),
6239        )
6240    }
6241
6242    pub fn coarse_unqualified_type_reference_may_resolve(
6243        &self,
6244        file: &ProjectFile,
6245        name: &str,
6246    ) -> bool {
6247        if name.is_empty() {
6248            return true;
6249        }
6250        self.visible_identifier_candidates(file, name)
6251            .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
6252            || self.visible_parser_alias_name_is_visible(file, name)
6253    }
6254
6255    #[allow(clippy::too_many_arguments)]
6256    pub fn structured_type_reference_may_resolve_to_target(
6257        &self,
6258        analyzer: &CppGraphSource<'_>,
6259        file: &ProjectFile,
6260        components: &[String],
6261        global: bool,
6262        lexical_scope: &[String],
6263        target: &CodeUnit,
6264    ) -> bool {
6265        if components.is_empty() {
6266            return true;
6267        }
6268        let Some(terminal) = components.last() else {
6269            return true;
6270        };
6271        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6272            .map(|qualified| qualified.join("::"))
6273            .collect::<Vec<_>>();
6274        let target_name = cpp_name_for(target);
6275        if qualified_tiers
6276            .iter()
6277            .any(|qualified| qualified == &target_name)
6278        {
6279            return true;
6280        }
6281
6282        let mut saw_shape_candidate = false;
6283        for candidate in self.visible_identifier_candidates(file, terminal) {
6284            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6285            {
6286                continue;
6287            }
6288            let candidate_name = cpp_name_for(candidate);
6289            let shape_matches = if global || components.len() > 1 {
6290                qualified_tiers
6291                    .iter()
6292                    .any(|qualified| qualified == &candidate_name)
6293            } else {
6294                true
6295            };
6296            if !shape_matches {
6297                continue;
6298            }
6299            saw_shape_candidate = true;
6300            if same_visible_symbol(candidate, target)
6301                || self.c_tag_declaration_family_matches_target(
6302                    analyzer,
6303                    file,
6304                    std::slice::from_ref(&candidate),
6305                    target,
6306                )
6307                || self.compatible_primary_template_redeclarations(candidate, target)
6308                || (declared_type_alias(analyzer, candidate)
6309                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6310            {
6311                return true;
6312            }
6313        }
6314
6315        !saw_shape_candidate
6316    }
6317
6318    /// A C tag's forward declaration and complete definition are one logical
6319    /// type even when their indexed signatures and source files differ. Keep
6320    /// this identity narrow: the complete declaration must be a top-level C
6321    /// tag, every visible candidate must be its physical forward declaration,
6322    /// and the parsed tag kind must agree. Different FQNs, competing complete
6323    /// definitions, aliases, and struct/union mismatches remain ambiguous.
6324    fn cached_c_tag_kind(
6325        &self,
6326        analyzer: &CppGraphSource<'_>,
6327        candidate: &CodeUnit,
6328    ) -> Option<CppCTagKind> {
6329        if let Some(kind) = self
6330            .c_tag_kind_cache
6331            .lock()
6332            .expect("C tag kind cache poisoned")
6333            .get(candidate)
6334        {
6335            return *kind;
6336        }
6337        let kind = indexed_c_tag_kind(analyzer, candidate);
6338        self.c_tag_kind_cache
6339            .lock()
6340            .expect("C tag kind cache poisoned")
6341            .insert(candidate.clone(), kind);
6342        kind
6343    }
6344
6345    fn cached_unique_c_tag_complete_definition(
6346        &self,
6347        analyzer: &CppGraphSource<'_>,
6348        target: &CodeUnit,
6349        target_tag: CppCTagKind,
6350    ) -> Option<CodeUnit> {
6351        if let Some(definition) = self
6352            .c_tag_complete_definition_cache
6353            .lock()
6354            .expect("C tag complete-definition cache poisoned")
6355            .get(target)
6356        {
6357            return definition.clone();
6358        }
6359        let complete_definitions = analyzer
6360            .definitions(&target.fq_name())
6361            .filter(|candidate| {
6362                candidate.is_class()
6363                    && !declared_type_alias(analyzer, candidate)
6364                    && is_c_source_file(candidate.source())
6365                    && analyzer.parent_of(candidate).is_none()
6366                    && cpp_class_declaration_strength(analyzer, candidate)
6367                        == CppClassDeclarationStrength::Full
6368                    && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6369            })
6370            .collect::<HashSet<_>>();
6371        let definition = (complete_definitions.len() == 1)
6372            .then(|| complete_definitions.into_iter().next())
6373            .flatten()
6374            .filter(|candidate| same_visible_symbol(candidate, target));
6375        self.c_tag_complete_definition_cache
6376            .lock()
6377            .expect("C tag complete-definition cache poisoned")
6378            .insert(target.clone(), definition.clone());
6379        definition
6380    }
6381
6382    pub fn c_tag_declaration_family_matches_target(
6383        &self,
6384        analyzer: &CppGraphSource<'_>,
6385        visible_from: &ProjectFile,
6386        candidates: &[&CodeUnit],
6387        target: &CodeUnit,
6388    ) -> bool {
6389        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6390            let candidate_evidence = candidates
6391                .iter()
6392                .map(|candidate| {
6393                    (
6394                        candidate.fq_name(),
6395                        candidate.source().rel_path().to_path_buf(),
6396                        cpp_class_declaration_strength(analyzer, candidate),
6397                        indexed_c_tag_kind(analyzer, candidate),
6398                        self.is_physically_visible(visible_from, candidate),
6399                    )
6400                })
6401                .collect::<Vec<_>>();
6402            eprintln!(
6403                "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6404                visible_from.rel_path().display(),
6405                target.fq_name(),
6406                target.source().rel_path().display(),
6407                cpp_class_declaration_strength(analyzer, target),
6408                indexed_c_tag_kind(analyzer, target),
6409            );
6410        }
6411        if candidates.is_empty()
6412            || !target.is_class()
6413            || declared_type_alias(analyzer, target)
6414            || !is_c_source_file(target.source())
6415            || analyzer.parent_of(target).is_some()
6416            || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6417        {
6418            return false;
6419        }
6420        let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6421            return false;
6422        };
6423        if self
6424            .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6425            .is_none()
6426        {
6427            return false;
6428        }
6429        let mut saw_visible_forward = false;
6430        for candidate in candidates.iter().copied() {
6431            if candidate == target {
6432                continue;
6433            }
6434            if !candidate.is_class()
6435                || declared_type_alias(analyzer, candidate)
6436                || candidate.fq_name() != target.fq_name()
6437                || analyzer.parent_of(candidate).is_some()
6438                || cpp_class_declaration_strength(analyzer, candidate)
6439                    != CppClassDeclarationStrength::Forward
6440                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6441                || !self.is_physically_visible(visible_from, candidate)
6442            {
6443                return false;
6444            }
6445            saw_visible_forward = true;
6446        }
6447        saw_visible_forward
6448    }
6449
6450    /// Collapse one visible complete C tag and its visible forward declarations
6451    /// before ordinary lexical resolution sees their different signatures as
6452    /// competing types. A second complete definition, a different FQN/tag
6453    /// kind, or an unknown declaration shape remains ambiguous.
6454    fn unique_c_tag_declaration_family(
6455        &self,
6456        analyzer: &CppGraphSource<'_>,
6457        visible_from: &ProjectFile,
6458        candidates: &[&CodeUnit],
6459    ) -> Option<CodeUnit> {
6460        let first = candidates.first()?;
6461        let target_fq_name = first.fq_name();
6462        let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6463        let mut full = None;
6464        let mut saw_forward = false;
6465        for candidate in candidates.iter().copied() {
6466            if !candidate.is_class()
6467                || declared_type_alias(analyzer, candidate)
6468                || candidate.fq_name() != target_fq_name
6469                || analyzer.parent_of(candidate).is_some()
6470                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6471            {
6472                return None;
6473            }
6474            match cpp_class_declaration_strength(analyzer, candidate) {
6475                CppClassDeclarationStrength::Full
6476                    if is_c_source_file(candidate.source())
6477                        && full.replace(candidate.clone()).is_none() => {}
6478                CppClassDeclarationStrength::Forward
6479                    if self.is_physically_visible(visible_from, candidate) =>
6480                {
6481                    saw_forward = true
6482                }
6483                _ => return None,
6484            }
6485        }
6486        if saw_forward { full } else { None }
6487    }
6488
6489    pub fn target_preserving_reference_namespace(
6490        &self,
6491        analyzer: &CppGraphSource<'_>,
6492        file: &ProjectFile,
6493        identifier: &str,
6494        target: &CodeUnit,
6495    ) -> Option<Vec<String>> {
6496        let mut namespace = None;
6497        for candidate in self.visible_identifier_candidates(file, identifier) {
6498            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6499            {
6500                continue;
6501            }
6502            if !(same_visible_symbol(candidate, target)
6503                || self.compatible_primary_template_redeclarations(candidate, target)
6504                || declared_type_alias(analyzer, candidate)
6505                    && self.structured_alias_primary_preserves_target(
6506                        analyzer, file, candidate, target,
6507                    ))
6508            {
6509                continue;
6510            }
6511            if namespace
6512                .as_ref()
6513                .is_some_and(|existing| existing != candidate.package_name())
6514            {
6515                return None;
6516            }
6517            namespace = Some(candidate.package_name().to_string());
6518        }
6519        let namespace = namespace?;
6520        Some(
6521            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6522                brokk_bifrost_core::analyzer::Language::Cpp,
6523                &namespace,
6524            ),
6525        )
6526    }
6527
6528    pub fn resolve_imported_type_candidate(
6529        &self,
6530        analyzer: &CppGraphSource<'_>,
6531        file: &ProjectFile,
6532        target: &CodeUnit,
6533        target_components: &[String],
6534        direct_target: Option<&CodeUnit>,
6535        preserve_alias: bool,
6536    ) -> LexicalTypeResolution {
6537        let candidates = [target];
6538        let resolution = if preserve_alias {
6539            TypeCandidateResolution::PreserveAlias
6540        } else {
6541            direct_target.map_or(
6542                TypeCandidateResolution::Canonical,
6543                TypeCandidateResolution::PreserveTarget,
6544            )
6545        };
6546        // One candidate goes in, so a failure here is never "choose one of
6547        // these": it is the alias chain leaving the index, which must answer
6548        // missing rather than ambiguous (#1828).
6549        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6550            Ok(unit) => LexicalTypeResolution::Resolved {
6551                unit,
6552                components: target_components.to_vec(),
6553                candidates: vec![target.clone()],
6554            },
6555            Err(failure) => failure.lexical_resolution(),
6556        }
6557    }
6558
6559    fn resolve_type_components_lexically_inner(
6560        &self,
6561        analyzer: &CppGraphSource<'_>,
6562        file: &ProjectFile,
6563        components: &[String],
6564        global: bool,
6565        lexical_scope: &[String],
6566        resolution: TypeCandidateResolution<'_>,
6567    ) -> LexicalTypeResolution {
6568        if components.is_empty() {
6569            return LexicalTypeResolution::Missing;
6570        }
6571        // A C++ class injects its own name into the class scope.  The indexed
6572        // FqName for that declaration is the class path itself (for example,
6573        // `n::raw_hash_set`), not a synthetic child named
6574        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
6575        // requested identifier to every scope component, so they cannot
6576        // represent that injected binding when the enclosing class is the
6577        // closest scope.  Recover the binding from the structured class path
6578        // before allowing lookup to fall through to an outer same-spelled
6579        // declaration.
6580        let mut injected = self.resolve_injected_class_name(
6581            analyzer,
6582            file,
6583            components,
6584            global,
6585            lexical_scope,
6586            resolution,
6587        );
6588        for qualified in lexical_component_tiers(components, global, lexical_scope) {
6589            let prefix_len = qualified.len().saturating_sub(components.len());
6590            if injected
6591                .as_ref()
6592                .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6593            {
6594                return injected
6595                    .take()
6596                    .expect("injected class resolution was just present")
6597                    .1;
6598            }
6599            let qualified_name = qualified.join("::");
6600            let candidates = self
6601                .type_candidates(file, &qualified_name)
6602                .into_iter()
6603                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6604                .collect::<Vec<_>>();
6605            if candidates.is_empty() {
6606                if !global && components.len() == 1 {
6607                    match self.resolve_inherited_type_for_lexical_scope(
6608                        analyzer,
6609                        file,
6610                        &qualified[..prefix_len],
6611                        &components[0],
6612                        resolution,
6613                    ) {
6614                        LexicalTypeResolution::Missing => {}
6615                        inherited => return inherited,
6616                    }
6617                }
6618                continue;
6619            }
6620            let candidates =
6621                self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6622            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6623                Ok(unit) => unit,
6624                Err(failure) => return failure.lexical_resolution(),
6625            };
6626            return LexicalTypeResolution::Resolved {
6627                unit,
6628                components: qualified,
6629                candidates: candidates.into_iter().cloned().collect(),
6630            };
6631        }
6632        LexicalTypeResolution::Missing
6633    }
6634
6635    fn resolve_injected_class_name(
6636        &self,
6637        analyzer: &CppGraphSource<'_>,
6638        file: &ProjectFile,
6639        components: &[String],
6640        global: bool,
6641        lexical_scope: &[String],
6642        resolution: TypeCandidateResolution<'_>,
6643    ) -> Option<(usize, LexicalTypeResolution)> {
6644        if global
6645            || components.len() != 1
6646            || file.rel_path().extension().is_some_and(|ext| ext == "c")
6647            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6648        {
6649            return None;
6650        }
6651        let name = components.first()?;
6652        let mut matches: Vec<&CodeUnit> = Vec::new();
6653        let mut owner_len = 0;
6654        for candidate in self.visible_identifier_candidates(file, name) {
6655            if !candidate.is_class()
6656                || declared_type_alias(analyzer, candidate)
6657                || candidate.identifier() != name
6658            {
6659                continue;
6660            }
6661            let candidate_scope = canonical_cpp_scope_components(candidate);
6662            if candidate_scope.len() > lexical_scope.len()
6663                || !lexical_scope.starts_with(&candidate_scope)
6664                || candidate_scope.last().is_none_or(|last| last != name)
6665            {
6666                continue;
6667            }
6668            if candidate_scope.len() > owner_len {
6669                owner_len = candidate_scope.len();
6670                matches.clear();
6671            }
6672            if candidate_scope.len() == owner_len
6673                && !matches
6674                    .iter()
6675                    .any(|existing| same_logical_symbol(existing, candidate))
6676            {
6677                matches.push(candidate);
6678            }
6679        }
6680        if matches.is_empty() {
6681            return None;
6682        }
6683        // The exact current class boundary is the most important injected-name
6684        // case. Ordinary lexical lookup appends the requested name and can
6685        // otherwise select a synthetic constructor-shaped child such as
6686        // `Portal::Impl::Impl` instead of the enclosing `Portal::Impl` class.
6687        // The global and single-component gates above keep qualified receiver
6688        // and static-qualifier contexts out of this recovery.
6689        let owner_components = lexical_scope[..owner_len].to_vec();
6690        let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6691        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6692            Ok(unit) => LexicalTypeResolution::Resolved {
6693                unit,
6694                components: owner_components,
6695                candidates: matches.into_iter().cloned().collect(),
6696            },
6697            Err(failure) => failure.lexical_resolution(),
6698        };
6699        Some((owner_len, resolution))
6700    }
6701
6702    fn resolve_inherited_type_for_lexical_scope(
6703        &self,
6704        analyzer: &CppGraphSource<'_>,
6705        file: &ProjectFile,
6706        lexical_scope: &[String],
6707        name: &str,
6708        resolution: TypeCandidateResolution<'_>,
6709    ) -> LexicalTypeResolution {
6710        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6711            return LexicalTypeResolution::Missing;
6712        };
6713        let lexical_owner_name = lexical_scope.join("::");
6714        if lexical_owner_name.is_empty() {
6715            return LexicalTypeResolution::Missing;
6716        }
6717        let owner_candidates = self
6718            .type_candidates(file, &lexical_owner_name)
6719            .into_iter()
6720            .filter(|candidate| {
6721                canonical_cpp_name_matches(candidate, &lexical_owner_name)
6722                    && !declared_type_alias(analyzer, candidate)
6723            })
6724            .collect::<Vec<_>>();
6725        if owner_candidates.is_empty() {
6726            return LexicalTypeResolution::Missing;
6727        }
6728        // A visible forward declaration and the physical class definition share
6729        // one FQN, but only the definition owns hierarchy facts. When lookup is
6730        // physically inside that definition, do not let an earlier header
6731        // forward declaration erase its base edges (#2240).
6732        let physical_owner_candidates = owner_candidates
6733            .iter()
6734            .copied()
6735            .filter(|candidate| candidate.source() == file)
6736            .collect::<Vec<_>>();
6737        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6738            owner_candidates
6739        } else {
6740            physical_owner_candidates
6741        };
6742        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6743            return LexicalTypeResolution::Ambiguous;
6744        };
6745
6746        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6747        let mut visited_owners = HashSet::default();
6748        while !frontier.is_empty() {
6749            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6750            let mut next_frontier = Vec::new();
6751            for owner in frontier {
6752                if !visited_owners.insert(owner.fq_name()) {
6753                    continue;
6754                }
6755                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6756                let candidates = self
6757                    .type_candidates(file, &qualified_name)
6758                    .into_iter()
6759                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6760                    .collect::<Vec<_>>();
6761                if candidates.is_empty() {
6762                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
6763                        if !next_frontier
6764                            .iter()
6765                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6766                        {
6767                            next_frontier.push(ancestor);
6768                        }
6769                    }
6770                    continue;
6771                }
6772                let candidates =
6773                    self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6774                let unit =
6775                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6776                        Ok(unit) => unit,
6777                        Err(failure) => return failure.lexical_resolution(),
6778                    };
6779                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6780            }
6781            if let Some((unit, candidates)) = level_matches.first().cloned() {
6782                let Some(first_declaration) = candidates.first() else {
6783                    return LexicalTypeResolution::Ambiguous;
6784                };
6785                if !level_matches.iter().all(|(_, declarations)| {
6786                    declarations
6787                        .iter()
6788                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
6789                }) {
6790                    return LexicalTypeResolution::Ambiguous;
6791                }
6792                let mut components = lexical_scope.to_vec();
6793                components.push(name.to_string());
6794                return LexicalTypeResolution::Resolved {
6795                    unit,
6796                    components,
6797                    candidates,
6798                };
6799            }
6800            frontier = next_frontier;
6801        }
6802        LexicalTypeResolution::Missing
6803    }
6804
6805    /// Resolve a base class through its injected class name at the nearest
6806    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
6807    ///
6808    /// A base whose canonical full definition cannot be pinned from `file` -
6809    /// a forward declaration the include closure completes with two different
6810    /// full definitions, or an alias chain that leaves the index - stops the
6811    /// walk only when that base is spelled `injected_name`. The mem-initializer
6812    /// names a base by that base's own injected class name, so a base spelled
6813    /// differently can never be the one it names, whichever definition it would
6814    /// have turned out to be; aborting the level on its account instead loses
6815    /// the sibling base that *is* named (#2543). A base that is spelled
6816    /// `injected_name` still fails closed, because choosing a deeper same-named
6817    /// ancestor over it would bind the initializer to the wrong constructor.
6818    /// The skipped base carries its own ancestors out of the walk with it: with
6819    /// no canonical unit, the repeated-base accounting below cannot tell one
6820    /// inherited path through it from two.
6821    pub fn inherited_injected_class_owner(
6822        &self,
6823        analyzer: &CppGraphSource<'_>,
6824        file: &ProjectFile,
6825        enclosing_owner: &CodeUnit,
6826        injected_name: &str,
6827    ) -> Option<CodeUnit> {
6828        let hierarchy = analyzer.type_hierarchy_provider()?;
6829        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6830        let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6831        while !frontier.is_empty() {
6832            let mut level_matches = Vec::new();
6833            let mut next_frontier = Vec::new();
6834            for raw_owner in frontier {
6835                let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6836                else {
6837                    if raw_owner.identifier() == injected_name {
6838                        return None;
6839                    }
6840                    continue;
6841                };
6842                let propagated = propagated_counts.entry(owner.clone()).or_default();
6843                if *propagated == 2 {
6844                    continue;
6845                }
6846                *propagated += 1;
6847                if owner.identifier() == injected_name {
6848                    level_matches.push(owner.clone());
6849                }
6850                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6851            }
6852            match level_matches.as_slice() {
6853                [owner] => return Some(owner.clone()),
6854                [_, ..] => return None,
6855                [] => {}
6856            }
6857            frontier = next_frontier;
6858        }
6859        None
6860    }
6861
6862    /// The one type the candidates name under `resolution`, or why they do not
6863    /// name one. The two preserving modes only ever reject candidates that
6864    /// disagree with each other, which is ambiguity; canonicalization can also
6865    /// fail because the alias chain leaves the index (#1828).
6866    fn resolve_type_candidates(
6867        &self,
6868        analyzer: &CppGraphSource<'_>,
6869        file: &ProjectFile,
6870        candidates: &[&CodeUnit],
6871        resolution: TypeCandidateResolution<'_>,
6872    ) -> Result<CodeUnit, TypeCandidateFailure> {
6873        if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6874            && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6875        {
6876            return Ok(unit);
6877        }
6878        match resolution {
6879            TypeCandidateResolution::Canonical => {
6880                self.canonical_type_candidate_resolution(analyzer, file, candidates)
6881            }
6882            TypeCandidateResolution::PreserveAlias => {
6883                // A generated index can retain identical alias spellings from
6884                // mutually exclusive headers. When the reference file
6885                // physically reaches exactly one of those source declarations,
6886                // include closure is the structured evidence that selects it;
6887                // treating the two source spellings as an overload set makes a
6888                // reachable alias appear ambiguous (#1844).
6889                let same_fqn_alias_family = candidates.len() > 1
6890                    && candidates.iter().all(|candidate| {
6891                        declared_type_alias(analyzer, candidate)
6892                            && same_logical_symbol(candidates[0], candidate)
6893                    })
6894                    && candidates
6895                        .iter()
6896                        .any(|candidate| candidate.source() != candidates[0].source());
6897                if same_fqn_alias_family {
6898                    let physically_visible = candidates
6899                        .iter()
6900                        .copied()
6901                        .filter(|candidate| self.is_physically_visible(file, candidate))
6902                        .collect::<Vec<_>>();
6903                    // The family is one logical declaration only when the
6904                    // reachable spellings agree. Two same-FQN aliases whose
6905                    // written targets differ (`using Choice = Canonical;` in
6906                    // one header, `using Choice = ::Canonical;` in another)
6907                    // are a genuine conflict, and choosing the first indexed
6908                    // one silently binds the reference to an arbitrary owner
6909                    // (#2398). Collapse only a single reachable declaration
6910                    // or reachable declarations with one structured target;
6911                    // everything else stays ambiguous below.
6912                    let one_structured_target = physically_visible.len() > 1
6913                        && physically_visible.iter().skip(1).all(|candidate| {
6914                            let target = self.structured_alias_target(analyzer, candidate);
6915                            target.is_some()
6916                                && target
6917                                    == self.structured_alias_target(analyzer, physically_visible[0])
6918                        });
6919                    if physically_visible.len() == 1 || one_structured_target {
6920                        return Ok(physically_visible[0].clone());
6921                    }
6922                }
6923                unique_type_candidate_preserving_alias(analyzer, file, candidates)
6924                    .ok_or(TypeCandidateFailure::Ambiguous)
6925            }
6926            TypeCandidateResolution::PreserveTarget(target) => self
6927                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6928                .ok_or(TypeCandidateFailure::Ambiguous),
6929        }
6930    }
6931
6932    fn candidates_for_type_resolution<'b>(
6933        &self,
6934        analyzer: &CppGraphSource<'_>,
6935        file: &ProjectFile,
6936        candidates: &[&'b CodeUnit],
6937        resolution: TypeCandidateResolution<'_>,
6938    ) -> Vec<&'b CodeUnit> {
6939        if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6940            let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6941            if compile_proven.len() == 1 {
6942                return compile_proven;
6943            }
6944        }
6945        candidates.to_vec()
6946    }
6947
6948    /// Narrow a same-name forward lookup to the declaration selected by the
6949    /// translation unit's compile command. The lexical resolver intentionally
6950    /// does not receive a reference node, so this is the only compile-context
6951    /// evidence available at that stage. A single selected candidate is safe:
6952    /// the caller still checks include activation and the reference's own
6953    /// guards before reporting the result as visible.
6954    fn compile_proven_type_candidates<'b>(
6955        &self,
6956        analyzer: &CppGraphSource<'_>,
6957        file: &ProjectFile,
6958        candidates: &[&'b CodeUnit],
6959    ) -> Vec<&'b CodeUnit> {
6960        let proven = self.compile_proven_guards(file);
6961        if proven.is_empty() {
6962            return Vec::new();
6963        }
6964        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6965            return Vec::new();
6966        };
6967        candidates
6968            .iter()
6969            .copied()
6970            .filter(|candidate| {
6971                let declaration_guards =
6972                    declaration_guard_requirements(analyzer, self.cpp, candidate);
6973                if declaration_guards.is_empty() {
6974                    return false;
6975                }
6976                if candidate.source() == file {
6977                    return declaration_guards.iter().any(|(_, required)| {
6978                        guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6979                    });
6980                }
6981                declaration_guards.iter().any(|(_, required)| {
6982                    self.foreign_declaration_reachable_from_compile_proven_guards(
6983                        file,
6984                        prepared.as_ref(),
6985                        candidate.source(),
6986                        required,
6987                        usize::MAX,
6988                    )
6989                })
6990            })
6991            .collect()
6992    }
6993
6994    pub fn resolve_callable_value_components_lexically(
6995        &self,
6996        analyzer: &CppGraphSource<'_>,
6997        file: &ProjectFile,
6998        owner_components: &[String],
6999        member_name: &str,
7000        global: bool,
7001        lexical_scope: &[String],
7002    ) -> LexicalCallableValueResolution {
7003        if owner_components.is_empty() || member_name.is_empty() {
7004            return LexicalCallableValueResolution::Missing;
7005        }
7006        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7007            let owner_name = qualified_owner.join("::");
7008            let type_candidates = self
7009                .type_candidates(file, &owner_name)
7010                .into_iter()
7011                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7012                .collect::<Vec<_>>();
7013            let resolved_type = if type_candidates.is_empty() {
7014                None
7015            } else {
7016                let Some(unit) =
7017                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7018                else {
7019                    return LexicalCallableValueResolution::Ambiguous;
7020                };
7021                Some(unit)
7022            };
7023
7024            let mut qualified_callable = qualified_owner;
7025            qualified_callable.push(member_name.to_string());
7026            let callable_name = qualified_callable.join("::");
7027            let free_function = self
7028                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7029                .into_iter()
7030                .find(|candidate| {
7031                    canonical_cpp_name_matches(candidate, &callable_name)
7032                        && type_owner_of(analyzer, candidate).is_none()
7033                })
7034                .cloned();
7035
7036            match (resolved_type, free_function) {
7037                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7038                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7039                (None, Some(function)) => {
7040                    return LexicalCallableValueResolution::FreeFunction(function);
7041                }
7042                (None, None) => {}
7043            }
7044        }
7045        LexicalCallableValueResolution::Missing
7046    }
7047
7048    fn resolve_type_for_declaration(
7049        &self,
7050        visible_from: &ProjectFile,
7051        declaration: &CodeUnit,
7052        raw_name: &str,
7053    ) -> Option<CodeUnit> {
7054        let normalized = normalize_reference_name(raw_name)?;
7055        if !normalized.contains("::")
7056            && let Some(namespace) = cpp_namespace_for(declaration)
7057        {
7058            for prefix in namespace_prefixes(&namespace) {
7059                let qualified = format!("{prefix}::{normalized}");
7060                if let Some(unit) = self
7061                    .type_candidates(visible_from, &qualified)
7062                    .into_iter()
7063                    .next()
7064                {
7065                    return Some(unit.clone());
7066                }
7067            }
7068        }
7069        self.resolve_type(visible_from, raw_name)
7070    }
7071
7072    fn resolve_unique_canonical_type_for_declaration(
7073        &self,
7074        analyzer: &CppGraphSource<'_>,
7075        visible_from: &ProjectFile,
7076        declaration: &CodeUnit,
7077        raw_name: &str,
7078    ) -> Option<CodeUnit> {
7079        let mut current = self.resolve_defining_type_for_declaration(
7080            analyzer,
7081            visible_from,
7082            declaration,
7083            raw_name,
7084        )?;
7085        let mut seen_aliases = HashSet::default();
7086        loop {
7087            let Some(target) = self.structured_alias_target(analyzer, &current) else {
7088                return current.is_class().then_some(current);
7089            };
7090            if matches!(target, StructuredAliasTarget::Builtin) {
7091                return current.is_class().then_some(current);
7092            }
7093            if !seen_aliases.insert(current.clone()) {
7094                return None;
7095            }
7096            current = self.resolve_structured_alias_target(visible_from, &current, &target)?;
7097        }
7098    }
7099
7100    pub fn canonical_type_unit(
7101        &self,
7102        analyzer: &CppGraphSource<'_>,
7103        visible_from: &ProjectFile,
7104        unit: &CodeUnit,
7105    ) -> Option<CodeUnit> {
7106        self.canonical_type_resolution(analyzer, visible_from, unit)
7107            .ok()
7108    }
7109
7110    /// Follow an alias only when it is visible at `reference`.
7111    ///
7112    /// The consumer need not spell the alias target. In particular, a
7113    /// conditional include can make `PublicPtr` visible at the reference while
7114    /// ordinary physical-include visibility is false. Prove the public alias
7115    /// with the reference's guard environment, then use the existing structured
7116    /// alias-chain resolver over the consumer's bounded declaration index.
7117    pub fn canonical_type_unit_in_context(
7118        &self,
7119        analyzer: &CppGraphSource<'_>,
7120        visible_from: &ProjectFile,
7121        reference: Node<'_>,
7122        unit: &CodeUnit,
7123    ) -> Option<CodeUnit> {
7124        if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7125        {
7126            return None;
7127        }
7128        self.canonical_type_resolution(analyzer, visible_from, unit)
7129            .ok()
7130    }
7131
7132    /// Follow `unit`'s alias chain to the class it names, or report why the
7133    /// chain does not end at one indexed class.
7134    ///
7135    /// A chain that leaves the index - an alias to a template parameter, to a
7136    /// standard-library type, or to any other declaration the workspace does
7137    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
7138    /// there is still nothing to choose between.
7139    fn canonical_type_resolution(
7140        &self,
7141        analyzer: &CppGraphSource<'_>,
7142        visible_from: &ProjectFile,
7143        unit: &CodeUnit,
7144    ) -> Result<CodeUnit, TypeCandidateFailure> {
7145        let mut current = unit.clone();
7146        let mut seen_aliases = HashSet::default();
7147        loop {
7148            let Some(target) = self.structured_alias_target(analyzer, &current) else {
7149                return current
7150                    .is_class()
7151                    .then_some(current)
7152                    .ok_or(TypeCandidateFailure::Unresolvable);
7153            };
7154            if matches!(target, StructuredAliasTarget::Builtin) {
7155                return current
7156                    .is_class()
7157                    .then_some(current)
7158                    .ok_or(TypeCandidateFailure::Unresolvable);
7159            }
7160            if !seen_aliases.insert(current.clone()) {
7161                return Err(TypeCandidateFailure::Unresolvable);
7162            }
7163            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
7164        }
7165    }
7166
7167    pub fn canonical_visible_full_type_unit(
7168        &self,
7169        analyzer: &CppGraphSource<'_>,
7170        visible_from: &ProjectFile,
7171        unit: &CodeUnit,
7172    ) -> Option<CodeUnit> {
7173        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7174        if cpp_class_declaration_strength(analyzer, &canonical)
7175            != CppClassDeclarationStrength::Forward
7176        {
7177            return Some(canonical);
7178        }
7179        let mut full = Vec::new();
7180        for candidate in self
7181            .visible_identifier_candidates(visible_from, canonical.identifier())
7182            .filter(|candidate| {
7183                candidate.is_class()
7184                    && candidate.fq_name() == canonical.fq_name()
7185                    && cpp_class_declaration_strength(analyzer, candidate)
7186                        == CppClassDeclarationStrength::Full
7187            })
7188        {
7189            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7190                full.push(candidate.clone());
7191            }
7192        }
7193        match full.len() {
7194            0 => Some(canonical),
7195            1 => full.pop(),
7196            _ => None,
7197        }
7198    }
7199
7200    fn resolve_structured_alias_target(
7201        &self,
7202        visible_from: &ProjectFile,
7203        declaration: &CodeUnit,
7204        target: &StructuredAliasTarget,
7205    ) -> Option<CodeUnit> {
7206        self.structured_alias_target_resolution(visible_from, declaration, target)
7207            .ok()
7208    }
7209
7210    fn structured_alias_target_resolution(
7211        &self,
7212        visible_from: &ProjectFile,
7213        declaration: &CodeUnit,
7214        target: &StructuredAliasTarget,
7215    ) -> Result<CodeUnit, TypeCandidateFailure> {
7216        let primary =
7217            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7218        let StructuredAliasTarget::Named { arguments, .. } = target else {
7219            return Err(TypeCandidateFailure::Unresolvable);
7220        };
7221        match arguments {
7222            Some(arguments) => self
7223                .resolve_template_arguments(visible_from, primary, arguments)
7224                .map_err(|error| match error {
7225                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7226                        TypeCandidateFailure::Ambiguous
7227                    }
7228                    _ => TypeCandidateFailure::Unresolvable,
7229                }),
7230            None => Ok(primary),
7231        }
7232    }
7233
7234    fn resolve_structured_alias_primary(
7235        &self,
7236        visible_from: &ProjectFile,
7237        declaration: &CodeUnit,
7238        target: &StructuredAliasTarget,
7239    ) -> Option<CodeUnit> {
7240        self.structured_alias_primary_resolution(visible_from, declaration, target)
7241            .ok()
7242    }
7243
7244    fn structured_alias_primary_resolution(
7245        &self,
7246        visible_from: &ProjectFile,
7247        declaration: &CodeUnit,
7248        target: &StructuredAliasTarget,
7249    ) -> Result<CodeUnit, TypeCandidateFailure> {
7250        let StructuredAliasTarget::Named {
7251            components, global, ..
7252        } = target
7253        else {
7254            return Err(TypeCandidateFailure::Unresolvable);
7255        };
7256        let qualified = components.join("::");
7257        let candidates = if *global {
7258            // `::A::B` anchors at the root scope, so a candidate whose
7259            // canonical path merely ends with the spelled components does not
7260            // qualify. Without this filter a global `::Canonical` target also
7261            // collects `alpha::Canonical`, the lookup reports a false
7262            // ambiguity, and the alias arm silently drops out of its
7263            // conflicting family instead of proving the conflict (#2398).
7264            let mut candidates = self.type_candidates(visible_from, &qualified);
7265            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7266            candidates
7267        } else {
7268            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7269        };
7270        logical_type_candidate(candidates)
7271    }
7272
7273    pub fn structured_alias_primary_preserves_target(
7274        &self,
7275        analyzer: &CppGraphSource<'_>,
7276        visible_from: &ProjectFile,
7277        candidate: &CodeUnit,
7278        target: &CodeUnit,
7279    ) -> bool {
7280        let mut current = candidate.clone();
7281        let mut seen = HashSet::default();
7282        let mut matched_target = false;
7283        loop {
7284            if same_visible_symbol(&current, target)
7285                || self.compatible_primary_template_redeclarations(&current, target)
7286            {
7287                matched_target = true;
7288            }
7289            if !seen.insert(current.clone()) {
7290                return false;
7291            }
7292            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7293                return matched_target;
7294            };
7295            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7296                return matched_target;
7297            };
7298            let Some(primary) =
7299                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7300            else {
7301                // A dependent member target such as `Detector<T>::type`
7302                // cannot be reduced to an indexed primary, but a preceding
7303                // structured alias hop may already have proven the requested
7304                // alias identity. Cycles still resolve a primary and are
7305                // rejected by `seen` above.
7306                return matched_target;
7307            };
7308            current = primary;
7309        }
7310    }
7311
7312    pub fn structured_class_alias_resolves_to_target(
7313        &self,
7314        analyzer: &CppGraphSource<'_>,
7315        visible_from: &ProjectFile,
7316        alias: &CodeUnit,
7317        target: &CodeUnit,
7318    ) -> bool {
7319        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7320            return false;
7321        };
7322        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7323            return false;
7324        };
7325        let StructuredAliasTarget::Named {
7326            components, global, ..
7327        } = &alias_target
7328        else {
7329            return false;
7330        };
7331        let lexical_scope = canonical_cpp_scope_components(&owner);
7332        match self.resolve_type_components_lexically_for_target(
7333            analyzer,
7334            visible_from,
7335            components,
7336            *global,
7337            &lexical_scope,
7338            target,
7339        ) {
7340            LexicalTypeResolution::Resolved {
7341                unit, candidates, ..
7342            } => {
7343                same_visible_symbol(&unit, target)
7344                    || self.same_template_member_identity(analyzer, &unit, target)
7345                    || candidates.iter().any(|candidate| {
7346                        same_visible_symbol(candidate, target)
7347                            || self.same_template_member_identity(analyzer, candidate, target)
7348                    })
7349            }
7350            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7351                self.structured_alias_primary_preserves_target(
7352                    analyzer,
7353                    visible_from,
7354                    alias,
7355                    target,
7356                ) || self.flattened_macro_namespace_alias_target_matches(
7357                    analyzer,
7358                    visible_from,
7359                    alias,
7360                    &alias_target,
7361                    target,
7362                )
7363            }
7364        }
7365    }
7366
7367    /// Return true when a class-owned alias names the requested type as one
7368    /// structured qualifier in its target path.
7369    ///
7370    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
7371    /// indexed class. Forward lookup can still retain `Primary` as its bounded
7372    /// canonical identity. Inverse lookup needs the same evidence when later
7373    /// references use only the alias spelling.
7374    pub fn structured_class_alias_path_preserves_target(
7375        &self,
7376        analyzer: &CppGraphSource<'_>,
7377        visible_from: &ProjectFile,
7378        alias: &CodeUnit,
7379        target: &CodeUnit,
7380    ) -> bool {
7381        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7382            return false;
7383        };
7384        let Some(StructuredAliasTarget::Named {
7385            components, global, ..
7386        }) = self.structured_alias_target(analyzer, alias)
7387        else {
7388            return false;
7389        };
7390        let lexical_scope = canonical_cpp_scope_components(&owner);
7391        (1..components.len()).rev().any(|component_count| {
7392            matches!(
7393                self.resolve_type_components_lexically_for_target(
7394                    analyzer,
7395                    visible_from,
7396                    &components[..component_count],
7397                    global,
7398                    &lexical_scope,
7399                    target,
7400                ),
7401                LexicalTypeResolution::Resolved {
7402                    ref unit,
7403                    ref candidates,
7404                    ..
7405                } if same_visible_symbol(unit, target)
7406                    || self.same_template_member_identity(analyzer, unit, target)
7407                    || candidates.iter().any(|candidate| {
7408                        same_visible_symbol(candidate, target)
7409                            || self.same_template_member_identity(analyzer, candidate, target)
7410                    })
7411            )
7412        })
7413    }
7414
7415    fn flattened_macro_namespace_alias_target_matches(
7416        &self,
7417        analyzer: &CppGraphSource<'_>,
7418        visible_from: &ProjectFile,
7419        alias: &CodeUnit,
7420        alias_target: &StructuredAliasTarget,
7421        target: &CodeUnit,
7422    ) -> bool {
7423        let StructuredAliasTarget::Named {
7424            components,
7425            global: false,
7426            arguments: None,
7427        } = alias_target
7428        else {
7429            return false;
7430        };
7431        let Some((target_name, namespace_components)) = components.split_last() else {
7432            return false;
7433        };
7434        if namespace_components.is_empty()
7435            || target_name != target.identifier()
7436            || alias.source() != target.source()
7437            || alias.source() != visible_from
7438            || !target.is_class()
7439            || declared_type_alias(analyzer, target)
7440        {
7441            return false;
7442        }
7443        if self
7444            .resolve_structured_alias_target(visible_from, alias, alias_target)
7445            .is_some()
7446        {
7447            return false;
7448        }
7449
7450        let alias_ranges = analyzer.ranges(alias);
7451        let target_ranges = analyzer.ranges(target);
7452        if alias_ranges.is_empty() || target_ranges.is_empty() {
7453            return false;
7454        }
7455        let alias_start = alias_ranges
7456            .iter()
7457            .map(|range| range.start_byte)
7458            .min()
7459            .expect("non-empty alias ranges have a minimum");
7460        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
7461            return false;
7462        };
7463        let root = prepared.tree().root_node();
7464        let has_matching_declaration = target_ranges
7465            .iter()
7466            .filter(|range| range.end_byte <= alias_start)
7467            .filter_map(|range| node_for_exact_range(root, range))
7468            .any(|node| {
7469                flattened_macro_namespace_components(node, prepared.source())
7470                    .is_some_and(|recovered| recovered == namespace_components)
7471            });
7472        if !has_matching_declaration {
7473            return false;
7474        }
7475
7476        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7477        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7478        guard_requirement_sets_match(&alias_guards, &target_guards)
7479    }
7480
7481    pub fn template_alias_arguments_preserve_target(
7482        &self,
7483        analyzer: &CppGraphSource<'_>,
7484        visible_from: &ProjectFile,
7485        alias: &CodeUnit,
7486        arguments: &[CppTemplateExpression],
7487        target: &CodeUnit,
7488    ) -> bool {
7489        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7490            return false;
7491        };
7492        if metadata.alias_target.is_none()
7493            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7494        {
7495            return false;
7496        }
7497        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7498    }
7499
7500    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7501        self.cpp_template_metadata
7502            .get(unit)
7503            .is_some_and(CppTemplateMetadata::is_primary)
7504    }
7505
7506    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7507        self.cpp_template_metadata
7508            .get(unit)
7509            .is_some_and(CppTemplateMetadata::is_specialization)
7510    }
7511
7512    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7513        same_visible_symbol(left, right)
7514            || self.compatible_primary_template_redeclarations(left, right)
7515    }
7516
7517    pub fn same_template_member_identity(
7518        &self,
7519        analyzer: &CppGraphSource<'_>,
7520        left: &CodeUnit,
7521        right: &CodeUnit,
7522    ) -> bool {
7523        if same_visible_symbol(left, right) {
7524            return true;
7525        }
7526        if left.kind() != right.kind()
7527            || left.identifier() != right.identifier()
7528            || left.signature() != right.signature()
7529        {
7530            return false;
7531        }
7532        let (Some(left_owner), Some(right_owner)) =
7533            (analyzer.parent_of(left), analyzer.parent_of(right))
7534        else {
7535            return false;
7536        };
7537        left_owner.is_class()
7538            && right_owner.is_class()
7539            && self.same_template_owner_identity(&left_owner, &right_owner)
7540    }
7541
7542    fn unique_canonical_type_candidate(
7543        &self,
7544        analyzer: &CppGraphSource<'_>,
7545        visible_from: &ProjectFile,
7546        candidates: &[&CodeUnit],
7547    ) -> Option<CodeUnit> {
7548        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7549            .ok()
7550    }
7551
7552    fn canonical_type_candidate_resolution(
7553        &self,
7554        analyzer: &CppGraphSource<'_>,
7555        visible_from: &ProjectFile,
7556        candidates: &[&CodeUnit],
7557    ) -> Result<CodeUnit, TypeCandidateFailure> {
7558        let mut canonical = Vec::new();
7559        for candidate in candidates {
7560            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7561            if canonical
7562                .iter()
7563                .any(|existing| same_visible_symbol(existing, &resolved))
7564            {
7565                continue;
7566            }
7567            if let Some(existing) = canonical.iter_mut().find(|existing| {
7568                self.compatible_primary_template_redeclarations(existing, &resolved)
7569            }) {
7570                // A forward declaration and its full primary-template
7571                // definition are one C++ type even when they live in
7572                // different headers and alpha-rename their parameters. The
7573                // target-preserving path already reconciles this family; do
7574                // the same for ordinary canonical lookup so an out-of-line
7575                // member's lexical owner is not made ambiguous by its own
7576                // forward declaration. Retain the strongest physical
7577                // declaration for later owner/range queries.
7578                if matches!(
7579                    (
7580                        cpp_class_declaration_strength(analyzer, existing),
7581                        cpp_class_declaration_strength(analyzer, &resolved),
7582                    ),
7583                    (
7584                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7585                        CppClassDeclarationStrength::Full,
7586                    ) | (
7587                        CppClassDeclarationStrength::Unknown,
7588                        CppClassDeclarationStrength::Forward,
7589                    )
7590                ) {
7591                    *existing = resolved;
7592                }
7593                continue;
7594            }
7595            canonical.push(resolved);
7596            if canonical.len() > 1 {
7597                return Err(TypeCandidateFailure::Ambiguous);
7598            }
7599        }
7600        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7601    }
7602
7603    pub fn unique_type_candidate_preserving_target(
7604        &self,
7605        analyzer: &CppGraphSource<'_>,
7606        visible_from: &ProjectFile,
7607        candidates: &[&CodeUnit],
7608        target: &CodeUnit,
7609    ) -> Option<CodeUnit> {
7610        // C++ headers often expose one logical type through mutually exclusive
7611        // physical declarations, for example a class in the fallback branch
7612        // and a `using` alias to the standard-library type in the configured
7613        // branch. The index intentionally retains both declarations so forward
7614        // lookup can report each target. Preserve the requested target when
7615        // that is the only ambiguity: every candidate has the same type kind,
7616        // exact canonical FQN, and source file, and the requested declaration
7617        // itself is one of the physical candidates. Do not merge same-named
7618        // declarations from different files or namespaces; those remain
7619        // ambiguous and fail closed below.
7620        if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7621            || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7622        {
7623            return Some(target.clone());
7624        }
7625        let mut resolved_candidates = Vec::new();
7626        for candidate in candidates {
7627            // An ifdef branch that aliases an unindexed system type (for
7628            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
7629            // canonicalized. That branch does not name `target`. Dropping it
7630            // keeps the branch that does. Failing the whole family here would
7631            // deny every usage of the reachable spelling (#2368).
7632            let Some(resolved) =
7633                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7634            else {
7635                continue;
7636            };
7637            if resolved_candidates
7638                .iter()
7639                .any(|existing| same_visible_symbol(existing, &resolved))
7640            {
7641                continue;
7642            }
7643            resolved_candidates.push(resolved);
7644        }
7645        match resolved_candidates.as_slice() {
7646            [] => None,
7647            [single] => Some(single.clone()),
7648            // The branches disagree about what the name aliases. When they are
7649            // spellings of one entity (#1845) that disagreement is a build
7650            // configuration, not a choice between types, so it must not deny
7651            // the requested target its reference.
7652            _ => self
7653                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7654                .map(|_| target.clone()),
7655        }
7656    }
7657
7658    /// The declaration a same-file same-FQN family stands for when a reference
7659    /// names `target`, or `None` when the candidates are not one family or the
7660    /// family does not name `target`.
7661    ///
7662    /// A translation unit cannot hold two different types under one qualified
7663    /// name, so several same-kind declarations of one FQN in one file are
7664    /// alternate spellings of one entity - the configuration branches of an
7665    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
7666    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
7667    /// targets differ; canonicalizing each branch on its own and then demanding
7668    /// agreement reports an ambiguity that denies every declaration in the
7669    /// family its usages (#1845). The family names `target` when it declares
7670    /// it, or when one branch's alias chain reaches it.
7671    ///
7672    /// Declarations in different files or namespaces are distinct entities and
7673    /// are deliberately excluded: their disagreement is a real ambiguity.
7674    pub fn same_fqn_type_spelling_for_target<'b>(
7675        &self,
7676        analyzer: &CppGraphSource<'_>,
7677        visible_from: &ProjectFile,
7678        candidates: &[&'b CodeUnit],
7679        target: &CodeUnit,
7680    ) -> Option<&'b CodeUnit> {
7681        let [first, rest @ ..] = candidates else {
7682            return None;
7683        };
7684        if rest.is_empty()
7685            || !rest.iter().all(|candidate| {
7686                candidate.kind() == first.kind()
7687                    && candidate.fq_name() == first.fq_name()
7688                    && candidate.source() == first.source()
7689            })
7690        {
7691            return None;
7692        }
7693        candidates
7694            .iter()
7695            .copied()
7696            .find(|candidate| same_symbol(candidate, target))
7697            .or_else(|| {
7698                candidates.iter().copied().find(|candidate| {
7699                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7700                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7701                })
7702            })
7703    }
7704
7705    pub fn alternate_same_fqn_type_declarations(
7706        &self,
7707        analyzer: &CppGraphSource<'_>,
7708        candidates: &[&CodeUnit],
7709        target: &CodeUnit,
7710    ) -> bool {
7711        let Some(first) = candidates.first() else {
7712            return false;
7713        };
7714        let same_api = first.kind() == target.kind()
7715            && first.fq_name() == target.fq_name()
7716            && first.source() == target.source()
7717            && candidates.iter().all(|candidate| {
7718                candidate.kind() == target.kind()
7719                    && candidate.fq_name() == target.fq_name()
7720                    && candidate.source() == target.source()
7721            })
7722            && candidates
7723                .iter()
7724                .any(|candidate| same_symbol(candidate, target))
7725            && candidates
7726                .iter()
7727                .any(|candidate| !same_logical_symbol(candidate, target));
7728        if !same_api {
7729            return false;
7730        }
7731
7732        let requirements = candidates
7733            .iter()
7734            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7735            .collect::<Vec<_>>();
7736        requirements.len() > 1
7737            && requirements
7738                .iter()
7739                .all(|requirement| !requirement.is_empty())
7740            && requirements.iter().enumerate().all(|(index, left)| {
7741                requirements[index + 1..].iter().all(|right| {
7742                    left.iter().all(|(_, left_guards)| {
7743                        right.iter().all(|(_, right_guards)| {
7744                            merge_preprocessor_guards(left_guards, right_guards).is_none()
7745                        })
7746                    })
7747                })
7748            })
7749    }
7750
7751    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7752        let mut pending = vec![terms.to_vec()];
7753        while let Some(branch_terms) = pending.pop() {
7754            let mut normalized = Vec::new();
7755            let mut covers_branch = false;
7756            for term in branch_terms {
7757                if term.iter().any(|guard| term.contains(&guard.negated())) {
7758                    continue;
7759                }
7760                if term.is_empty() {
7761                    covers_branch = true;
7762                    break;
7763                }
7764                if !normalized.iter().any(|existing| existing == &term) {
7765                    normalized.push(term);
7766                }
7767            }
7768            if covers_branch {
7769                continue;
7770            }
7771            let Some(split_guard) = normalized
7772                .iter()
7773                .flat_map(|term| term.iter())
7774                .next()
7775                .cloned()
7776            else {
7777                return false;
7778            };
7779            let negated_guard = split_guard.negated();
7780            let mut when_defined = Vec::new();
7781            let mut when_undefined = Vec::new();
7782            for term in normalized {
7783                if term.contains(&negated_guard) {
7784                    // This term cannot hold when `split_guard` is true.
7785                } else if term.contains(&split_guard) {
7786                    let mut reduced = term.clone();
7787                    reduced.remove(&split_guard);
7788                    when_defined.push(reduced);
7789                } else {
7790                    when_defined.push(term.clone());
7791                }
7792                if term.contains(&split_guard) {
7793                    // This term cannot hold when `split_guard` is false.
7794                } else if term.contains(&negated_guard) {
7795                    let mut reduced = term;
7796                    reduced.remove(&negated_guard);
7797                    when_undefined.push(reduced);
7798                } else {
7799                    when_undefined.push(term);
7800                }
7801            }
7802            pending.push(when_defined);
7803            pending.push(when_undefined);
7804        }
7805        true
7806    }
7807
7808    /// The byte range of the one `#if` family with a terminal `#else` that holds
7809    /// every physical declaration of every candidate, or `None` when they do not
7810    /// share one such family.
7811    ///
7812    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
7813    /// whose macros changed between declarations. Require every physical range to
7814    /// belong to one syntax-tree family with a terminal `#else` before the terms
7815    /// can prove branch coverage.
7816    fn declarations_share_exhaustive_conditional_family(
7817        &self,
7818        analyzer: &CppGraphSource<'_>,
7819        candidates: &[&CodeUnit],
7820    ) -> Option<(usize, usize)> {
7821        let mut family_range = None;
7822        for candidate in candidates {
7823            let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
7824            let root = prepared.tree().root_node();
7825            let mut candidate_family = None;
7826            for range in analyzer.ranges(candidate) {
7827                let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
7828                let family = preprocessor_conditional_family_for_declaration(node)?;
7829                let key = (family.start_byte(), family.end_byte());
7830                if candidate_family.is_some_and(|existing| existing != key) {
7831                    return None;
7832                }
7833                candidate_family = Some(key);
7834            }
7835            let candidate_family = candidate_family?;
7836            if family_range.is_some_and(|existing| existing != candidate_family) {
7837                return None;
7838            }
7839            family_range = Some(candidate_family);
7840        }
7841        family_range
7842    }
7843
7844    pub fn complementary_same_fqn_type_declarations(
7845        &self,
7846        analyzer: &CppGraphSource<'_>,
7847        candidates: &[&CodeUnit],
7848        target: &CodeUnit,
7849    ) -> bool {
7850        if candidates.len() < 2
7851            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7852            || self
7853                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7854                .is_none()
7855        {
7856            return false;
7857        }
7858        Self::preprocessor_guard_terms_cover_all_paths(
7859            &self.declaration_family_guard_terms(analyzer, candidates),
7860        )
7861    }
7862
7863    fn declaration_family_guard_terms(
7864        &self,
7865        analyzer: &CppGraphSource<'_>,
7866        candidates: &[&CodeUnit],
7867    ) -> Vec<HashSet<PreprocessorGuard>> {
7868        candidates
7869            .iter()
7870            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7871            .map(|(_, guards)| guards)
7872            .collect()
7873    }
7874
7875    /// A callable name declared on every branch of one completed `#if`/`#else`
7876    /// family is declared on every configuration path, so a reference below the
7877    /// whole family sees one of the branches whatever the preprocessor decides.
7878    /// Answer the family's end byte: only past `#endif` is every branch's
7879    /// declaration behind the reference.
7880    ///
7881    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
7882    /// and shares both of its primitives. It does not require two distinct
7883    /// `CodeUnit`s: branches that declare the same signature can collapse into
7884    /// one unit carrying one physical range per branch.
7885    ///
7886    /// The branches are alternate spellings of one declaration, never competing
7887    /// declarations, so only the first branch stands for the family. Reporting
7888    /// every branch as visible would turn a name the source declares exactly
7889    /// once into an ambiguity between build configurations.
7890    fn exhaustive_guard_family_activation(
7891        &self,
7892        analyzer: &CppGraphSource<'_>,
7893        prepared: &PreparedSyntaxTree,
7894        candidate: &CodeUnit,
7895        reference: &CallableReferenceContext<'_>,
7896    ) -> Option<usize> {
7897        // Branch coverage says nothing about scope: a block-local declaration
7898        // stays invisible however many branches declare it.
7899        if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
7900            return None;
7901        }
7902        let family = self
7903            .visible_identifier_candidates(candidate.source(), candidate.identifier())
7904            .filter(|peer| {
7905                peer.kind() == candidate.kind()
7906                    && peer.fq_name() == candidate.fq_name()
7907                    && peer.source() == candidate.source()
7908            })
7909            .collect::<Vec<_>>();
7910        let (_, family_end) =
7911            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7912        if !Self::preprocessor_guard_terms_cover_all_paths(
7913            &self.declaration_family_guard_terms(analyzer, &family),
7914        ) {
7915            return None;
7916        }
7917        // A reference whose own guards pick one branch already reaches that
7918        // branch through the ordinary same-guard path; the family must not
7919        // resurrect the branch the reference contradicts.
7920        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7921            .iter()
7922            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7923        {
7924            return None;
7925        }
7926        (first_declaration_byte(analyzer, candidate)?
7927            == family
7928                .iter()
7929                .filter_map(|peer| first_declaration_byte(analyzer, peer))
7930                .min()?)
7931        .then_some(family_end)
7932    }
7933
7934    fn type_candidate_preserving_target(
7935        &self,
7936        analyzer: &CppGraphSource<'_>,
7937        visible_from: &ProjectFile,
7938        candidate: &CodeUnit,
7939        target: &CodeUnit,
7940    ) -> Option<CodeUnit> {
7941        let mut current = candidate.clone();
7942        let mut matched_target = same_visible_symbol(&current, target)
7943            || self.compatible_primary_template_redeclarations(&current, target);
7944        let mut seen = HashSet::default();
7945        loop {
7946            if !seen.insert(current.clone()) {
7947                return None;
7948            }
7949            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7950                return matched_target
7951                    .then(|| target.clone())
7952                    .or_else(|| current.is_class().then_some(current));
7953            };
7954            if self.flattened_macro_namespace_alias_target_matches(
7955                analyzer,
7956                visible_from,
7957                &current,
7958                &alias_target,
7959                target,
7960            ) {
7961                return Some(target.clone());
7962            }
7963            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7964                return matched_target
7965                    .then(|| target.clone())
7966                    .or_else(|| current.is_class().then_some(current));
7967            }
7968            // A non-template alias can name a template alias with explicit
7969            // arguments (for example, `using Result = Expected<int>`).  When
7970            // the requested target is that alias's primary declaration, keep
7971            // the primary identity before expanding the RHS arguments.  The
7972            // expansion would otherwise canonicalize through the underlying
7973            // implementation type and lose the target spelling used by the
7974            // forward resolver.
7975            if !self.cpp_template_metadata.contains_key(&current)
7976                && let Some(primary) =
7977                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7978                && (same_visible_symbol(&primary, target)
7979                    || self.compatible_primary_template_redeclarations(&primary, target))
7980            {
7981                return Some(target.clone());
7982            }
7983            if same_visible_symbol(&current, target) {
7984                return Some(target.clone());
7985            }
7986            if self.cpp_template_metadata.contains_key(&current) {
7987                return None;
7988            }
7989            let Some(next) =
7990                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
7991            else {
7992                return matched_target.then(|| target.clone());
7993            };
7994            current = next;
7995            matched_target |= same_visible_symbol(&current, target)
7996                || self.compatible_primary_template_redeclarations(&current, target);
7997        }
7998    }
7999
8000    fn compatible_primary_template_redeclarations(
8001        &self,
8002        left: &CodeUnit,
8003        right: &CodeUnit,
8004    ) -> bool {
8005        let (Some(left_metadata), Some(right_metadata)) = (
8006            self.cpp_template_metadata.get(left),
8007            self.cpp_template_metadata.get(right),
8008        ) else {
8009            return false;
8010        };
8011        left_metadata.primary_fq_name == right_metadata.primary_fq_name
8012            && left_metadata.is_primary()
8013            && right_metadata.is_primary()
8014            && cpp_reconcile_primary_template_parameters(
8015                &[(left, left_metadata), (right, right_metadata)],
8016                right,
8017            )
8018            .is_some()
8019    }
8020
8021    fn alias_candidate_may_preserve_target(
8022        &self,
8023        analyzer: &CppGraphSource<'_>,
8024        visible_from: &ProjectFile,
8025        candidate: &CodeUnit,
8026        target: &CodeUnit,
8027    ) -> bool {
8028        let mut current = candidate.clone();
8029        let mut seen = HashSet::default();
8030        loop {
8031            if same_visible_symbol(&current, target)
8032                || self.compatible_primary_template_redeclarations(&current, target)
8033            {
8034                return true;
8035            }
8036            if self.cpp_template_metadata.contains_key(&current) {
8037                return true;
8038            }
8039            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8040                return false;
8041            };
8042            let StructuredAliasTarget::Named {
8043                components,
8044                global,
8045                arguments,
8046            } = alias_target
8047            else {
8048                return false;
8049            };
8050            if arguments.is_some() || !seen.insert(current.clone()) {
8051                return true;
8052            }
8053            let qualified = components.join("::");
8054            let next = if global {
8055                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8056            } else {
8057                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
8058            };
8059            let Some(next) = next else {
8060                return true;
8061            };
8062            current = next;
8063        }
8064    }
8065
8066    fn alias_candidate_structurally_reaches_target(
8067        &self,
8068        analyzer: &CppGraphSource<'_>,
8069        visible_from: &ProjectFile,
8070        candidate: &CodeUnit,
8071        target: &CodeUnit,
8072    ) -> bool {
8073        let mut current = candidate.clone();
8074        let mut seen = HashSet::default();
8075        loop {
8076            if same_visible_symbol(&current, target)
8077                || self.compatible_primary_template_redeclarations(&current, target)
8078                || self
8079                    .cpp_template_metadata
8080                    .get(&current)
8081                    .zip(self.cpp_template_metadata.get(target))
8082                    .is_some_and(|(current, target)| {
8083                        current.primary_fq_name == target.primary_fq_name
8084                    })
8085            {
8086                return true;
8087            }
8088            if !seen.insert(current.clone()) {
8089                return false;
8090            }
8091            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8092                return false;
8093            };
8094            if let StructuredAliasTarget::Named { components, .. } = &alias_target
8095                && components[..components.len().saturating_sub(1)]
8096                    .iter()
8097                    .any(|component| {
8098                        self.type_reference_component_directly_names_target(component, target)
8099                    })
8100                && self.structured_class_alias_path_preserves_target(
8101                    analyzer,
8102                    visible_from,
8103                    &current,
8104                    target,
8105                )
8106            {
8107                return true;
8108            }
8109            if matches!(alias_target, StructuredAliasTarget::Builtin) {
8110                return false;
8111            }
8112            let Some(primary) =
8113                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
8114            else {
8115                return false;
8116            };
8117            current = primary;
8118        }
8119    }
8120
8121    /// Whether one indexed alias component in a qualified type reference has
8122    /// a structured path to `target`.
8123    ///
8124    /// Alias terminal names such as `type` are deliberately common. A
8125    /// file-wide name set cannot distinguish `Gen<T>::type`, whose indexed RHS
8126    /// names the target, from thousands of unrelated `Traits<T>::type`
8127    /// references. Match every qualified prefix against the alias's canonical
8128    /// scope before following its structured RHS. An unresolved dependent RHS
8129    /// fails this coarse gate only when no other component names the target;
8130    /// references such as `Identity<Target>::type` are admitted by that target
8131    /// component before this helper is called.
8132    pub fn qualified_alias_reference_may_reach_target(
8133        &self,
8134        analyzer: &CppGraphSource<'_>,
8135        file: &ProjectFile,
8136        components: &[String],
8137        global: bool,
8138        target: &CodeUnit,
8139    ) -> bool {
8140        for end in 0..components.len() {
8141            let spelled = &components[..=end];
8142            for candidate in self
8143                .visible_identifier_candidates(file, &components[end])
8144                .filter(|candidate| declared_type_alias(analyzer, candidate))
8145            {
8146                let candidate_components = canonical_cpp_scope_components(candidate);
8147                let shape_matches = if global {
8148                    candidate_components == spelled
8149                } else {
8150                    candidate_components.ends_with(spelled)
8151                };
8152                if shape_matches
8153                    && self.alias_candidate_structurally_reaches_target(
8154                        analyzer, file, candidate, target,
8155                    )
8156                {
8157                    return true;
8158                }
8159            }
8160        }
8161        false
8162    }
8163
8164    /// Every indexed type declaration `raw_name` names when it is written in
8165    /// `declaration`'s namespace: the innermost enclosing namespace that holds
8166    /// the name wins, otherwise the name is looked up unqualified.
8167    fn type_candidates_for_declaration<'b>(
8168        &'b self,
8169        visible_from: &ProjectFile,
8170        declaration: &CodeUnit,
8171        raw_name: &str,
8172    ) -> Vec<&'b CodeUnit> {
8173        let Some(normalized) = normalize_reference_name(raw_name) else {
8174            return Vec::new();
8175        };
8176        if let Some(namespace) = cpp_namespace_for(declaration) {
8177            for prefix in namespace_prefixes(&namespace) {
8178                let qualified = format!("{prefix}::{normalized}");
8179                let candidates = self.type_candidates(visible_from, &qualified);
8180                if !candidates.is_empty() {
8181                    return candidates;
8182                }
8183            }
8184        }
8185        self.type_candidates(visible_from, &normalized)
8186    }
8187
8188    fn resolve_unique_type_for_declaration(
8189        &self,
8190        visible_from: &ProjectFile,
8191        declaration: &CodeUnit,
8192        raw_name: &str,
8193    ) -> Option<CodeUnit> {
8194        unique_logical_type_candidate(self.type_candidates_for_declaration(
8195            visible_from,
8196            declaration,
8197            raw_name,
8198        ))
8199    }
8200
8201    /// The type `raw_name` names at `declaration`, answered with the
8202    /// declaration that defines it.
8203    ///
8204    /// C spells one type twice. `typedef struct T T;` in the header that
8205    /// publishes the handle indexes an incomplete `struct T` beside the
8206    /// complete `struct T { ... }` in the header that declares the members, and
8207    /// both carry the same kind and fully qualified name, so
8208    /// [`logical_type_candidate`] is free to answer with either. mbedtls writes
8209    /// exactly that: `include/mbedtls/ssl.h` forward-declares
8210    /// `mbedtls_ssl_handshake_params` for the pointer member
8211    /// `mbedtls_ssl_handshake_params *MBEDTLS_PRIVATE(handshake)` while
8212    /// `library/ssl_misc.h` defines its fields. A receiver typed from a field
8213    /// declaration is asked for its members on the next link of the chain, and
8214    /// only the definition owns them (#2982).
8215    ///
8216    /// Competing definitions leave nothing to choose between, so keep the
8217    /// logical answer rather than picking one of them.
8218    fn resolve_defining_type_for_declaration(
8219        &self,
8220        analyzer: &CppGraphSource<'_>,
8221        visible_from: &ProjectFile,
8222        declaration: &CodeUnit,
8223        raw_name: &str,
8224    ) -> Option<CodeUnit> {
8225        let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8226        let logical = unique_logical_type_candidate(candidates.clone())?;
8227        let mut defining = candidates.into_iter().filter(|candidate| {
8228            candidate.is_class()
8229                && cpp_class_declaration_strength(analyzer, candidate)
8230                    == CppClassDeclarationStrength::Full
8231        });
8232        match (defining.next(), defining.next()) {
8233            (Some(unique_definition), None) => Some(unique_definition.clone()),
8234            _ => Some(logical),
8235        }
8236    }
8237
8238    pub fn resolves_to_type(
8239        &self,
8240        analyzer: &CppGraphSource<'_>,
8241        file: &ProjectFile,
8242        raw_name: &str,
8243        target: &CodeUnit,
8244    ) -> bool {
8245        let Some(normalized) = normalize_reference_name(raw_name) else {
8246            return false;
8247        };
8248        let candidates = self.type_candidates(file, &normalized);
8249        if candidates.is_empty() {
8250            return self.parser_alias_resolves_to_type(file, raw_name, target);
8251        }
8252        let Some(resolved) =
8253            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8254        else {
8255            return false;
8256        };
8257        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8258    }
8259
8260    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8261        let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
8262        let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
8263        match resolved.kind() {
8264            CodeUnitType::Class => Some(resolved),
8265            _ if is_type_alias(&resolved) => self.alias_target(&resolved),
8266            _ => None,
8267        }
8268    }
8269
8270    /// Whether two callable declarations declare one function.
8271    ///
8272    /// [`same_logical_symbol`] compares the persisted signature strings, which
8273    /// embed each parameter type exactly as it was spelled. A header
8274    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
8275    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
8276    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
8277    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
8278    /// This resolves the written parameter names before comparing them and
8279    /// reports the same answer the language does for the cases it can prove.
8280    ///
8281    /// Everything it cannot prove stays two symbols: a template declaration, a
8282    /// parameter with no comparable shape, a name that resolves on one side
8283    /// only, and an alias chain it cannot follow safely (#2010).
8284    pub fn same_logical_callable(
8285        &self,
8286        analyzer: &CppGraphSource<'_>,
8287        left: &CodeUnit,
8288        right: &CodeUnit,
8289    ) -> bool {
8290        if same_logical_symbol(left, right) {
8291            return true;
8292        }
8293        if left.kind() != right.kind()
8294            || !left.is_callable()
8295            || !right.is_callable()
8296            || left.fq_name() != right.fq_name()
8297        {
8298            return false;
8299        }
8300        // A template declaration and its out-of-line body can also diverge
8301        // outside the parameter list - `template <class T>` against
8302        // `template <typename T>` - and the template head is part of the
8303        // persisted signature. Deciding template-head equivalence is a
8304        // separate question, so templates keep string identity.
8305        if self.callable_is_template_declaration(analyzer, left)
8306            || self.callable_is_template_declaration(analyzer, right)
8307        {
8308            return false;
8309        }
8310        let (Some(left_comparable), Some(right_comparable)) = (
8311            self.callable_comparable(analyzer, left),
8312            self.callable_comparable(analyzer, right),
8313        ) else {
8314            return false;
8315        };
8316        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
8317        // return type and requires-clause are part of C++ callable identity and
8318        // an out-of-line definition repeats them verbatim, so they must agree
8319        // as written.
8320        if left_comparable.suffix != right_comparable.suffix
8321            || left_comparable.shapes.len() != right_comparable.shapes.len()
8322        {
8323            return false;
8324        }
8325        left_comparable
8326            .shapes
8327            .iter()
8328            .zip(right_comparable.shapes.iter())
8329            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8330                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8331                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8332                    self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8333                }
8334                // An unstructured parameter records that the reduction failed,
8335                // not that the two spellings mean the same type, so it agrees
8336                // with nothing - including another unstructured parameter.
8337                _ => false,
8338            })
8339    }
8340
8341    /// Compare two parameter shapes node by node with an explicit paired stack.
8342    ///
8343    /// Shape variants and cv-qualifiers must agree exactly at every level; only
8344    /// the named leaves may be spelled differently, and they agree when they
8345    /// resolve to one type declaration.
8346    fn comparable_shapes_agree(
8347        &self,
8348        analyzer: &CppGraphSource<'_>,
8349        left: &CppComparableParameter,
8350        right: &CppComparableParameter,
8351    ) -> bool {
8352        let mut stack = vec![(left.root(), right.root())];
8353        while let Some((left_index, right_index)) = stack.pop() {
8354            match (left.node(left_index), right.node(right_index)) {
8355                (
8356                    CppComparableNode::Named {
8357                        name: left_name,
8358                        primitive: left_primitive,
8359                        konst: left_konst,
8360                        volatil: left_volatil,
8361                    },
8362                    CppComparableNode::Named {
8363                        name: right_name,
8364                        primitive: right_primitive,
8365                        konst: right_konst,
8366                        volatil: right_volatil,
8367                    },
8368                ) => {
8369                    if left_konst != right_konst
8370                        || left_volatil != right_volatil
8371                        || left_primitive != right_primitive
8372                        || !self.comparable_names_agree(
8373                            analyzer,
8374                            left_name,
8375                            right_name,
8376                            *left_primitive,
8377                        )
8378                    {
8379                        return false;
8380                    }
8381                }
8382                (
8383                    CppComparableNode::Pointer {
8384                        inner: left_inner,
8385                        konst: left_konst,
8386                        volatil: left_volatil,
8387                    },
8388                    CppComparableNode::Pointer {
8389                        inner: right_inner,
8390                        konst: right_konst,
8391                        volatil: right_volatil,
8392                    },
8393                ) => {
8394                    if left_konst != right_konst || left_volatil != right_volatil {
8395                        return false;
8396                    }
8397                    stack.push((*left_inner, *right_inner));
8398                }
8399                (
8400                    CppComparableNode::Reference { inner: left_inner },
8401                    CppComparableNode::Reference { inner: right_inner },
8402                )
8403                | (
8404                    CppComparableNode::Array { inner: left_inner },
8405                    CppComparableNode::Array { inner: right_inner },
8406                ) => stack.push((*left_inner, *right_inner)),
8407                (
8408                    CppComparableNode::Generic {
8409                        base: left_base,
8410                        arguments: left_arguments,
8411                    },
8412                    CppComparableNode::Generic {
8413                        base: right_base,
8414                        arguments: right_arguments,
8415                    },
8416                ) => {
8417                    if left_arguments.len() != right_arguments.len() {
8418                        return false;
8419                    }
8420                    stack.push((*left_base, *right_base));
8421                    stack.extend(
8422                        left_arguments.iter().zip(right_arguments.iter()).map(
8423                            |(left_argument, right_argument)| (*left_argument, *right_argument),
8424                        ),
8425                    );
8426                }
8427                _ => return false,
8428            }
8429        }
8430        true
8431    }
8432
8433    /// Whether two written type names denote one type.
8434    ///
8435    /// A primitive denotes the same type in every scope, so its recorded
8436    /// lexical scope is noise and its spelling decides. A nominal name is
8437    /// resolved on each side independently: two resolved names agree when they
8438    /// reach one type declaration, and two unresolved names agree only on
8439    /// exact agreement of what was written, which is no weaker than the
8440    /// whole-signature string equality this comparison replaces. Resolution on
8441    /// one side only is evidence of difference, never of agreement.
8442    fn comparable_names_agree(
8443        &self,
8444        analyzer: &CppGraphSource<'_>,
8445        left: &StructuredTypeName,
8446        right: &StructuredTypeName,
8447        primitive: bool,
8448    ) -> bool {
8449        if primitive {
8450            return left.path() == right.path();
8451        }
8452        match (
8453            self.comparable_name_terminal(analyzer, left),
8454            self.comparable_name_terminal(analyzer, right),
8455        ) {
8456            (Some(left_terminal), Some(right_terminal)) => {
8457                same_logical_symbol(&left_terminal, &right_terminal)
8458            }
8459            (None, None) => {
8460                left.path() == right.path() && left.is_absolute() == right.is_absolute()
8461            }
8462            _ => false,
8463        }
8464    }
8465
8466    /// The class declaration a written type name denotes, or `None` when the
8467    /// workspace cannot prove one.
8468    ///
8469    /// The lookup is a closure-independent lexical-scope prefix walk over the
8470    /// stored C++ identifier index rather than a visibility lookup: a body's
8471    /// `.cpp` is almost never in the reference file's include closure. Any name
8472    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
8473    /// cannot invent a type the compiler could not see; `using`-directives are
8474    /// not modelled, and a name that needs one stays unresolved.
8475    fn comparable_name_terminal(
8476        &self,
8477        analyzer: &CppGraphSource<'_>,
8478        name: &StructuredTypeName,
8479    ) -> Option<CodeUnit> {
8480        let mut current = self.comparable_name_declaration(analyzer, name)?;
8481        let mut visited = HashSet::default();
8482        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8483            // The alias question is asked before the class question, and
8484            // through `declared_type_alias` rather than `is_type_alias`,
8485            // because extraction records `using A8 = A7;` as a *Class* unit
8486            // whose signature is the alias declaration. Reading the kind first
8487            // would end the chase on the alias itself and report an alias
8488            // spelling and its underlying class as two types (#2010).
8489            if !declared_type_alias(analyzer, &current) {
8490                return current.is_class().then_some(current);
8491            }
8492            if !visited.insert(current.clone()) {
8493                return None;
8494            }
8495            let signature = current.signature()?;
8496            // `cpp_alias_declaration_target_text` reads the declaration's
8497            // `type` field only, so `typedef Foo *Bar` reports `Foo` and the
8498            // pointer is silently dropped. Substituting such an alias would
8499            // fuse `f(Bar)` and `f(Foo)`, which are two functions.
8500            if cpp_alias_declaration_adds_indirection(signature) {
8501                return None;
8502            }
8503            let raw_target = cpp_alias_declaration_target_text(signature)?;
8504            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
8505        }
8506        None
8507    }
8508
8509    /// The declaration one alias hop lands on: the type `raw_target` names,
8510    /// looked up from the alias declaration's own enclosing namespace.
8511    ///
8512    /// The hop takes the same closure-independent prefix walk the first lookup
8513    /// took, and deliberately not `resolve_type_for_declaration`: that one
8514    /// answers out of the `VisibilityIndex`, which is rooted at the reference
8515    /// file, while the alias declaration this hop starts from is reached
8516    /// through the workspace definition index and its file need not be in that
8517    /// root's include closure - where the visibility lookup answers nothing and
8518    /// the chase would stop on the alias itself (#2010).
8519    fn comparable_alias_target(
8520        &self,
8521        analyzer: &CppGraphSource<'_>,
8522        alias: &CodeUnit,
8523        raw_target: &str,
8524    ) -> Option<CodeUnit> {
8525        // `raw_target` is the alias declaration's written type text, so it is a
8526        // plain `::`-joined qualified-id: the same domain the shared symbol-path
8527        // parser reads, and the same leading `::` that marks an absolute name
8528        // everywhere else this crate normalizes a reference.
8529        let absolute = raw_target.trim_start().starts_with("::");
8530        let normalized = normalize_reference_name(raw_target)?;
8531        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8532            brokk_bifrost_core::analyzer::Language::Cpp,
8533            &normalized,
8534        );
8535        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8536            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8537                brokk_bifrost_core::analyzer::Language::Cpp,
8538                &namespace,
8539            )
8540        });
8541        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8542        self.comparable_name_declaration(analyzer, &name)
8543    }
8544
8545    /// The one type declaration `name` names, by enclosing scope, innermost
8546    /// first.
8547    ///
8548    /// The first prefix depth that names anything decides: an inner scope hides
8549    /// an outer one, so a match there is the answer even when an outer scope
8550    /// also declares the name. Several logically distinct declarations at that
8551    /// depth are an ambiguity this comparison must not guess at.
8552    fn comparable_name_declaration(
8553        &self,
8554        analyzer: &CppGraphSource<'_>,
8555        name: &StructuredTypeName,
8556    ) -> Option<CodeUnit> {
8557        // The same parameter-name pair is commonly compared once for every
8558        // physical declaration in a candidate set. Keep the index lookup under
8559        // the cache lock so concurrent workers cannot all miss the same key
8560        // and repeat the workspace candidate filter. `None` is a meaningful
8561        // result too: unresolved and ambiguous names must remain unresolved on
8562        // every later comparison.
8563        let mut cache = self
8564            .comparable_name_declarations
8565            .lock()
8566            .expect("C++ comparable name declaration cache poisoned");
8567        if let Some(cached) = cache.get(name) {
8568            return cached.clone();
8569        }
8570        let interner = segment_interner();
8571        let identifier = name.path().last()?;
8572        let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8573        let first_depth = if name.is_absolute() {
8574            0
8575        } else {
8576            name.lexical_scope().len()
8577        };
8578        let mut resolved = None;
8579        for depth in (0..=first_depth).rev() {
8580            let mut structured = FqName::new();
8581            for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8582                structured.push(interner.intern(component, SegmentKind::Unknown));
8583            }
8584            let mut candidates = candidates_by_identifier
8585                .iter()
8586                .filter(|unit| unit.fq().same_segment_texts(&structured))
8587                .filter(|unit| {
8588                    unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8589                })
8590                .cloned();
8591            let Some(first) = candidates.next() else {
8592                continue;
8593            };
8594            resolved = candidates
8595                .all(|unit| same_logical_symbol(&unit, &first))
8596                .then_some(first);
8597            break;
8598        }
8599        cache.insert(name.clone(), resolved.clone());
8600        resolved
8601    }
8602
8603    /// The comparison inputs of one callable declaration, extracted once.
8604    ///
8605    /// The comparison itself runs only when two candidates share kind and fully
8606    /// qualified name but not signature, which is rare; re-reading the same
8607    /// declaration for every pair in a candidate set is not.
8608    fn callable_comparable(
8609        &self,
8610        analyzer: &CppGraphSource<'_>,
8611        unit: &CodeUnit,
8612    ) -> Option<Arc<ExtractedComparable>> {
8613        if let Some(cached) = self
8614            .callable_comparables
8615            .lock()
8616            .expect("C++ callable comparable cache poisoned")
8617            .get(unit)
8618            .cloned()
8619        {
8620            return cached;
8621        }
8622        let extracted = self
8623            .extract_callable_comparable(analyzer, unit)
8624            .map(Arc::new);
8625        self.callable_comparables
8626            .lock()
8627            .expect("C++ callable comparable cache poisoned")
8628            .insert(unit.clone(), extracted.clone());
8629        extracted
8630    }
8631
8632    fn extract_callable_comparable(
8633        &self,
8634        analyzer: &CppGraphSource<'_>,
8635        unit: &CodeUnit,
8636    ) -> Option<ExtractedComparable> {
8637        let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
8638        let root = prepared.tree().root_node();
8639        let declarator = analyzer
8640            .ranges(unit)
8641            .into_iter()
8642            .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
8643        Some(ExtractedComparable {
8644            // One question about one declarator: indexing the file's tree would
8645            // cost more than the walk it saves.
8646            shapes: cpp_comparable_parameter_shapes(
8647                declarator,
8648                prepared.source(),
8649                &ParentIndex::unindexed(),
8650            ),
8651            suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
8652        })
8653    }
8654
8655    pub fn canonical_type_for_reference(
8656        &self,
8657        file: &ProjectFile,
8658        raw_name: &str,
8659    ) -> Option<CodeUnit> {
8660        let resolved = self.resolve_type(file, raw_name)?;
8661        self.alias_target(&resolved).or(Some(resolved))
8662    }
8663
8664    pub fn parser_alias_resolves_to_type(
8665        &self,
8666        file: &ProjectFile,
8667        raw_name: &str,
8668        target: &CodeUnit,
8669    ) -> bool {
8670        let Some(alias_name) = normalize_reference_name(raw_name) else {
8671            return false;
8672        };
8673        self.parser_alias_name_may_resolve_to_target(file, &alias_name, target)
8674    }
8675
8676    #[cfg(any(test, feature = "test-support"))]
8677    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8678        self.visible_source_files_by_root
8679            .get(file)
8680            .cloned()
8681            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8682    }
8683
8684    #[cfg(any(test, feature = "test-support"))]
8685    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8686        self.alias_source_parse_counts
8687            .lock()
8688            .expect("alias source parse count lock")
8689            .get(file)
8690            .copied()
8691            .unwrap_or(0)
8692    }
8693
8694    #[cfg(any(test, feature = "test-support"))]
8695    pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8696        self.parser_alias_fallback_files.load(Ordering::Relaxed)
8697    }
8698
8699    pub fn resolve_named(
8700        &self,
8701        file: &ProjectFile,
8702        raw_name: &str,
8703        kind: TargetKind,
8704    ) -> Option<CodeUnit> {
8705        let normalized = normalize_reference_name(raw_name)?;
8706        self.named_candidates_for_normalized(file, &normalized, kind)
8707            .into_iter()
8708            .next()
8709            .cloned()
8710    }
8711
8712    pub fn contains_named_symbol(
8713        &self,
8714        file: &ProjectFile,
8715        raw_name: &str,
8716        kind: TargetKind,
8717        target: &CodeUnit,
8718    ) -> bool {
8719        let Some(normalized) = normalize_reference_name(raw_name) else {
8720            return false;
8721        };
8722        self.named_candidates_for_normalized(file, &normalized, kind)
8723            .into_iter()
8724            .any(|unit| {
8725                matches_kind_for_lookup(unit, kind)
8726                    && reference_matches_unit(&normalized, unit)
8727                    && same_visible_symbol(unit, target)
8728            })
8729    }
8730
8731    pub fn named_candidates(
8732        &self,
8733        file: &ProjectFile,
8734        raw_name: &str,
8735        kind: TargetKind,
8736    ) -> Vec<CodeUnit> {
8737        let Some(normalized) = normalize_reference_name(raw_name) else {
8738            return Vec::new();
8739        };
8740        self.named_candidates_for_normalized(file, &normalized, kind)
8741            .into_iter()
8742            .cloned()
8743            .collect()
8744    }
8745
8746    pub fn resolve_known_non_target(
8747        &self,
8748        file: &ProjectFile,
8749        raw_name: &str,
8750        kind: TargetKind,
8751        target: &CodeUnit,
8752    ) -> bool {
8753        let Some(normalized) = normalize_reference_name(raw_name) else {
8754            return false;
8755        };
8756        normalized.contains("::")
8757            && self
8758                .named_candidates_for_normalized(file, &normalized, kind)
8759                .into_iter()
8760                .any(|unit| {
8761                    matches_kind_for_lookup(unit, kind)
8762                        && reference_matches_unit(&normalized, unit)
8763                        && !same_visible_symbol(unit, target)
8764                })
8765    }
8766
8767    pub fn resolve_call_return_binding(
8768        &self,
8769        analyzer: &CppGraphSource<'_>,
8770        file: &ProjectFile,
8771        raw_name: &str,
8772        arity: usize,
8773        lexical_namespace: Option<&str>,
8774        direct_type: Option<&CodeUnit>,
8775    ) -> Option<CppScanBinding> {
8776        let normalized = normalize_reference_name(raw_name)?;
8777        let mut candidates = Vec::new();
8778        for function in
8779            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8780        {
8781            if cpp_callable_arity(analyzer, function).accepts(arity)
8782                && !direct_type.is_some_and(|direct_type| {
8783                    self.callable_is_constructor_declaration(analyzer, function)
8784                        && type_owner_of(analyzer, function)
8785                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8786                })
8787            {
8788                candidates.push(function.clone());
8789            }
8790        }
8791        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8792        unanimous_return_binding(analyzer, self, file, &candidates)
8793    }
8794
8795    pub fn resolve_call_return_binding_without_arity(
8796        &self,
8797        analyzer: &CppGraphSource<'_>,
8798        file: &ProjectFile,
8799        raw_name: &str,
8800        lexical_namespace: Option<&str>,
8801        direct_type: Option<&CodeUnit>,
8802    ) -> (bool, Option<CppScanBinding>) {
8803        let Some(normalized) = normalize_reference_name(raw_name) else {
8804            return (false, None);
8805        };
8806        let mut candidates = self
8807            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8808            .into_iter()
8809            .filter(|function| {
8810                function.is_function()
8811                    && !direct_type.is_some_and(|direct_type| {
8812                        self.callable_is_constructor_declaration(analyzer, function)
8813                            && type_owner_of(analyzer, function)
8814                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8815                    })
8816            })
8817            .cloned()
8818            .collect::<Vec<_>>();
8819        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8820        let has_candidates = !candidates.is_empty();
8821        (
8822            has_candidates,
8823            unanimous_return_binding(analyzer, self, file, &candidates),
8824        )
8825    }
8826
8827    pub fn visible_identifier_candidates<'b>(
8828        &'b self,
8829        file: &ProjectFile,
8830        identifier: &str,
8831    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8832        self.visible_by_identifier
8833            .get(file)
8834            .and_then(|by_name| by_name.get(identifier))
8835            .into_iter()
8836            .flatten()
8837    }
8838
8839    /// Whether a grammar component is the direct spelling of `target`.
8840    ///
8841    /// Concrete template specializations are indexed with their arguments in
8842    /// `identifier`, while a reference's name component contains only the
8843    /// primary name. Argument matching remains the later resolver's job.
8844    pub fn type_reference_component_directly_names_target(
8845        &self,
8846        component: &str,
8847        target: &CodeUnit,
8848    ) -> bool {
8849        component == target.identifier()
8850            || self
8851                .cpp_template_metadata
8852                .get(target)
8853                .is_some_and(|metadata| component == metadata.primary_name)
8854    }
8855
8856    /// Return terminal reference names that can denote `target` from `file`.
8857    ///
8858    /// The indexed candidate table covers ordinary declarations and aliases;
8859    /// Parser-only aliases are tested lazily when their spelling is actually
8860    /// encountered in a scanned type node. Enumerating them here would parse
8861    /// every source in the include closure even when the target's direct name
8862    /// is the only spelling present in the file.
8863    pub fn visible_type_reference_component_names_for_target(
8864        &self,
8865        analyzer: &CppGraphSource<'_>,
8866        file: &ProjectFile,
8867        target: &CodeUnit,
8868    ) -> HashSet<String> {
8869        let mut names = HashSet::from_iter([target.identifier().to_string()]);
8870        if let Some(metadata) = self.cpp_template_metadata.get(target) {
8871            names.insert(metadata.primary_name.clone());
8872        }
8873
8874        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8875            for (identifier, candidates) in by_identifier {
8876                if candidates.iter().any(|candidate| {
8877                    (candidate.is_class()
8878                        && (same_visible_symbol(candidate, target)
8879                            || self.compatible_primary_template_redeclarations(candidate, target)))
8880                        || (declared_type_alias(analyzer, candidate)
8881                            && self.alias_candidate_structurally_reaches_target(
8882                                analyzer, file, candidate, target,
8883                            ))
8884                }) {
8885                    names.insert(identifier.clone());
8886                }
8887            }
8888        }
8889
8890        names
8891    }
8892
8893    pub fn indexed_structural_class_scope(
8894        &self,
8895        file: &ProjectFile,
8896        class: Node<'_>,
8897        source: &str,
8898    ) -> Option<Vec<String>> {
8899        let key = (file.clone(), class.start_byte(), class.end_byte());
8900        if let Some(cached) = self
8901            .indexed_structural_class_scopes
8902            .lock()
8903            .expect("C++ indexed structural-class scope cache poisoned")
8904            .get(&key)
8905            .cloned()
8906        {
8907            return cached;
8908        }
8909        let resolved = (|| {
8910            let name = class.child_by_field_name("name")?;
8911            let identifier = if name.kind() == "template_type" {
8912                node_text(name.child_by_field_name("name")?, source).to_string()
8913            } else {
8914                let mut components = Vec::new();
8915                append_cpp_name_components(name, source, &mut components)?;
8916                components.last()?.clone()
8917            };
8918            let visible = self
8919                .visible_identifier_candidates(file, &identifier)
8920                .cloned()
8921                .collect::<Vec<_>>();
8922            let mut visible = visible;
8923            for candidate in
8924                self.visible_by_file
8925                    .get(file)
8926                    .into_iter()
8927                    .flatten()
8928                    .filter(|candidate| {
8929                        self.cpp_template_metadata
8930                            .get(candidate)
8931                            .is_some_and(|metadata| metadata.primary_name == identifier)
8932                    })
8933            {
8934                if !visible
8935                    .iter()
8936                    .any(|existing| same_logical_symbol(existing, candidate))
8937                {
8938                    visible.push(candidate.clone());
8939                }
8940            }
8941            // Built once per call rather than per candidate; `cpp_source` rebuilds
8942            // the five-field source from the same `self.cpp` on every call.
8943            let cpp_source = self.cpp_source();
8944            let candidates = visible
8945                .iter()
8946                .filter(|candidate| {
8947                    candidate.source() == file
8948                        && candidate.is_class()
8949                        && !declared_type_alias(&cpp_source, candidate)
8950                        && self.cpp.ranges(candidate).iter().any(|range| {
8951                            range.start_byte <= class.start_byte()
8952                                && class.end_byte() <= range.end_byte
8953                        })
8954                })
8955                .collect::<Vec<_>>();
8956            let owner = if name.kind() == "template_type" {
8957                let expected = normalize_cpp_whitespace(node_text(name, source));
8958                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
8959                let exact = candidates
8960                    .iter()
8961                    .copied()
8962                    .filter(|candidate| {
8963                        candidate
8964                            .fq()
8965                            .segments()
8966                            .iter()
8967                            .rev()
8968                            .find_map(|&segment| {
8969                                let (text, kind) = interner.resolve(segment);
8970                                matches!(
8971                                    kind,
8972                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
8973                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
8974                                )
8975                                .then_some(text)
8976                            })
8977                            .is_some_and(|text| text == expected)
8978                    })
8979                    .collect::<Vec<_>>();
8980                unique_logical_type_candidate(exact)
8981                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
8982            } else {
8983                unique_logical_type_candidate(candidates)?
8984            };
8985            Some(canonical_cpp_scope_components(&owner))
8986        })();
8987        self.indexed_structural_class_scopes
8988            .lock()
8989            .expect("C++ indexed structural-class scope cache poisoned")
8990            .insert(key, resolved.clone());
8991        resolved
8992    }
8993
8994    pub fn indexed_enclosing_owner_scope(
8995        &self,
8996        analyzer: &CppGraphSource<'_>,
8997        file: &ProjectFile,
8998        node: Node<'_>,
8999    ) -> Option<Vec<String>> {
9000        let anchor = std::iter::successors(Some(node), |current| current.parent())
9001            .find(|current| {
9002                matches!(
9003                    current.kind(),
9004                    "function_definition"
9005                        | "class_specifier"
9006                        | "struct_specifier"
9007                        | "union_specifier"
9008                )
9009            })
9010            .unwrap_or(node);
9011        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9012        if let Some(cached) = self
9013            .indexed_enclosing_owner_scopes
9014            .lock()
9015            .expect("C++ indexed enclosing-owner scope cache poisoned")
9016            .get(&key)
9017            .cloned()
9018        {
9019            return cached;
9020        }
9021        let resolved = (|| {
9022            let range = Range {
9023                start_byte: node.start_byte(),
9024                end_byte: node.end_byte(),
9025                start_line: node.start_position().row,
9026                end_line: node.end_position().row,
9027            };
9028            let start = analyzer.enclosing_code_unit(file, &range)?;
9029            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9030                start,
9031                |unit| self.cached_precise_parent_of(analyzer, unit),
9032            )
9033            .find(|unit| {
9034                unit.is_class()
9035                    && !analyzer
9036                        .type_alias_provider()
9037                        .is_some_and(|provider| provider.is_type_alias(unit))
9038            })?;
9039            Some(canonical_cpp_scope_components(&owner))
9040        })();
9041        self.indexed_enclosing_owner_scopes
9042            .lock()
9043            .expect("C++ indexed enclosing-owner scope cache poisoned")
9044            .insert(key, resolved.clone());
9045        resolved
9046    }
9047
9048    fn cached_precise_parent_of(
9049        &self,
9050        analyzer: &CppGraphSource<'_>,
9051        code_unit: &CodeUnit,
9052    ) -> Option<CodeUnit> {
9053        if let Some(cached) = self
9054            .precise_parent_cache
9055            .lock()
9056            .expect("C++ precise-parent cache poisoned")
9057            .get(code_unit)
9058            .cloned()
9059        {
9060            return cached;
9061        }
9062        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9063        self.precise_parent_cache
9064            .lock()
9065            .expect("C++ precise-parent cache poisoned")
9066            .insert(code_unit.clone(), resolved.clone());
9067        resolved
9068    }
9069
9070    pub fn callable_is_constructor_declaration(
9071        &self,
9072        analyzer: &CppGraphSource<'_>,
9073        candidate: &CodeUnit,
9074    ) -> bool {
9075        if !candidate.is_function() {
9076            return false;
9077        }
9078        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9079            return false;
9080        };
9081        let root = prepared.tree().root_node();
9082        let candidate_ranges = analyzer.ranges(candidate);
9083        let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
9084            let mut current = root
9085                .descendant_for_byte_range(range.start_byte, range.end_byte)
9086                .and_then(|node| node.parent());
9087            while let Some(node) = current {
9088                if matches!(
9089                    node.kind(),
9090                    "class_specifier" | "struct_specifier" | "union_specifier"
9091                ) {
9092                    return node
9093                        .child_by_field_name("name")
9094                        .map(|name| terminal_name(node_text(name, prepared.source())))
9095                        .is_some_and(|name| name == candidate.identifier());
9096                }
9097                current = node.parent();
9098            }
9099            false
9100        });
9101        if enclosed_by_matching_type {
9102            return true;
9103        }
9104        let indexed_containment = analyzer
9105            .declarations(candidate.source())
9106            .into_iter()
9107            .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
9108            .any(|owner| {
9109                analyzer.ranges(&owner).iter().any(|owner_range| {
9110                    candidate_ranges.iter().any(|candidate_range| {
9111                        owner_range.start_byte <= candidate_range.start_byte
9112                            && candidate_range.end_byte <= owner_range.end_byte
9113                    })
9114                })
9115            });
9116        if indexed_containment {
9117            return true;
9118        }
9119        let metadata = analyzer.signature_metadata(candidate);
9120        !metadata.is_empty()
9121            && metadata
9122                .iter()
9123                .all(|signature| signature.return_type_text().is_none())
9124    }
9125
9126    /// Whether a callable declaration is a class-template deduction guide.
9127    ///
9128    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
9129    /// type field whose function declarator owns a trailing return type. This
9130    /// structured shape distinguishes a guide from both a constructor (no
9131    /// trailing return) and an ordinary trailing-return function (an `auto`
9132    /// type field).
9133    pub fn callable_is_deduction_guide_declaration(
9134        &self,
9135        analyzer: &CppGraphSource<'_>,
9136        candidate: &CodeUnit,
9137    ) -> bool {
9138        if !candidate.is_function() {
9139            return false;
9140        }
9141        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9142            return false;
9143        };
9144        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
9145            .into_iter()
9146            .any(|declaration| {
9147                if declaration.kind() != "declaration"
9148                    || declaration.child_by_field_name("type").is_some()
9149                {
9150                    return false;
9151                }
9152                let Some(declarator) = declaration.child_by_field_name("declarator") else {
9153                    return false;
9154                };
9155                if declarator.kind() != "function_declarator" {
9156                    return false;
9157                }
9158                let mut cursor = declarator.walk();
9159                let has_trailing_return = declarator
9160                    .named_children(&mut cursor)
9161                    .any(|child| child.kind() == "trailing_return_type");
9162                has_trailing_return
9163                    && declarator_name_node(declarator).is_some_and(|name| {
9164                        node_text(name, prepared.source()) == candidate.identifier()
9165                    })
9166            })
9167    }
9168
9169    /// Whether a callable occurrence is directly wrapped by a C++ template
9170    /// declaration. This deliberately inspects declaration syntax instead of
9171    /// inferring template status from the rendered signature.
9172    pub fn callable_is_template_declaration(
9173        &self,
9174        analyzer: &CppGraphSource<'_>,
9175        candidate: &CodeUnit,
9176    ) -> bool {
9177        if !candidate.is_function() {
9178            return false;
9179        }
9180        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9181            return false;
9182        };
9183        let root = prepared.tree().root_node();
9184        analyzer.ranges(candidate).iter().any(|range| {
9185            let Some(node) = node_for_exact_range(root, range)
9186                .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
9187            else {
9188                return false;
9189            };
9190            node.parent().is_some_and(|parent| {
9191                parent.kind() == "template_declaration"
9192                    && parent
9193                        .named_child(parent.named_child_count().saturating_sub(1))
9194                        .is_some_and(|declaration| same_node(declaration, node))
9195            })
9196        })
9197    }
9198
9199    pub fn type_name_candidates<'b>(
9200        &'b self,
9201        file: &ProjectFile,
9202        normalized: &str,
9203    ) -> Vec<&'b CodeUnit> {
9204        self.candidate_units(file, normalized, TargetKind::Type)
9205    }
9206
9207    pub fn visible_members_for_owner_name<'b>(
9208        &'b self,
9209        file: &ProjectFile,
9210        owner: &CodeUnit,
9211        name: &str,
9212    ) -> Vec<&'b CodeUnit> {
9213        self.visible_identifier_candidates(file, name)
9214            .filter(|unit| {
9215                // Structured owner pop on the unit's own `fq()` (shared with
9216                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
9217                // string.
9218                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9219                    .is_some_and(|parent| parent == owner.fq_name())
9220            })
9221            .collect()
9222    }
9223
9224    pub fn visible_member_for_owner_name(
9225        &self,
9226        file: &ProjectFile,
9227        owner: &CodeUnit,
9228        name: &str,
9229    ) -> VisibleMemberResolution {
9230        let candidates = self.visible_members_for_owner_name(file, owner, name);
9231        let mut callables = Vec::new();
9232        let mut non_callable = None;
9233        for candidate in candidates {
9234            if candidate.is_function() {
9235                callables.push(candidate.clone());
9236            } else if non_callable.is_none() {
9237                non_callable = Some(candidate.clone());
9238            }
9239        }
9240        match (callables.is_empty(), non_callable) {
9241            (false, None) => VisibleMemberResolution::Callable(callables),
9242            (true, Some(_)) => VisibleMemberResolution::NonCallable,
9243            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9244            (true, None) => VisibleMemberResolution::Missing,
9245        }
9246    }
9247
9248    fn field_declared_type_fact(
9249        &self,
9250        analyzer: &CppGraphSource<'_>,
9251        field: &CodeUnit,
9252    ) -> Option<DeclaredFieldTypeFact> {
9253        if let Some(cached) = self
9254            .field_type_facts
9255            .lock()
9256            .expect("C++ field type fact cache poisoned")
9257            .get(field)
9258            .cloned()
9259        {
9260            return cached;
9261        }
9262        let decoded = decode_field_declared_type_fact(analyzer, field);
9263        self.field_type_facts
9264            .lock()
9265            .expect("C++ field type fact cache poisoned")
9266            .insert(field.clone(), decoded.clone());
9267        decoded
9268    }
9269
9270    fn structured_alias_target(
9271        &self,
9272        analyzer: &CppGraphSource<'_>,
9273        unit: &CodeUnit,
9274    ) -> Option<StructuredAliasTarget> {
9275        if let Some(cached) = self
9276            .structured_alias_targets
9277            .lock()
9278            .expect("C++ structured alias target cache poisoned")
9279            .get(unit)
9280            .cloned()
9281        {
9282            return cached;
9283        }
9284        let decoded = decode_structured_alias_target(analyzer, unit);
9285        self.structured_alias_targets
9286            .lock()
9287            .expect("C++ structured alias target cache poisoned")
9288            .insert(unit.clone(), decoded.clone());
9289        decoded
9290    }
9291
9292    pub fn type_candidates<'b>(
9293        &'b self,
9294        file: &ProjectFile,
9295        normalized: &str,
9296    ) -> Vec<&'b CodeUnit> {
9297        let mut candidates = self
9298            .candidate_units(file, normalized, TargetKind::Type)
9299            .into_iter()
9300            .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
9301            .collect::<Vec<_>>();
9302        dedup_unit_refs(&mut candidates);
9303        candidates
9304    }
9305
9306    pub fn named_candidates_for_normalized<'b>(
9307        &'b self,
9308        file: &ProjectFile,
9309        normalized: &str,
9310        kind: TargetKind,
9311    ) -> Vec<&'b CodeUnit> {
9312        let mut candidates = self
9313            .candidate_units(file, normalized, kind)
9314            .into_iter()
9315            .filter(|unit| {
9316                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9317            })
9318            .collect::<Vec<_>>();
9319        dedup_unit_refs(&mut candidates);
9320        candidates
9321    }
9322
9323    pub fn candidate_units<'b>(
9324        &'b self,
9325        file: &ProjectFile,
9326        normalized: &str,
9327        kind: TargetKind,
9328    ) -> Vec<&'b CodeUnit> {
9329        if normalized.contains("::") {
9330            // `normalized` comes from `normalize_cpp_reference_text`, which
9331            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
9332            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
9333            // kept intact by the shared splitter's operator merge — the same
9334            // domain `cpp_reference_fqn_candidates` below already parses with
9335            // the shared splitter. Re-tokenizing and taking the last segment
9336            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
9337            // scan exactly.
9338            let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9339                brokk_bifrost_core::analyzer::Language::Cpp,
9340                normalized,
9341            )
9342            .pop() else {
9343                return Vec::new();
9344            };
9345            let fqns = cpp_reference_fqn_candidates(normalized, kind);
9346            return self
9347                .visible_identifier_candidates(file, &identifier)
9348                .filter(|unit| {
9349                    #[cfg(any(test, feature = "test-support"))]
9350                    self.qualified_candidate_inspections
9351                        .fetch_add(1, Ordering::Relaxed);
9352                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9353                        || canonical_cpp_name_matches(unit, normalized)
9354                })
9355                .collect();
9356        }
9357        self.visible_identifier_candidates(file, normalized)
9358            .collect()
9359    }
9360
9361    #[cfg(any(test, feature = "test-support"))]
9362    pub fn reset_qualified_candidate_inspections(&self) {
9363        self.qualified_candidate_inspections
9364            .store(0, Ordering::Relaxed);
9365    }
9366
9367    #[cfg(any(test, feature = "test-support"))]
9368    pub fn qualified_candidate_inspections(&self) -> usize {
9369        self.qualified_candidate_inspections.load(Ordering::Relaxed)
9370    }
9371
9372    #[cfg(any(test, feature = "test-support"))]
9373    pub fn visibility_identifier_lookup_count(&self) -> usize {
9374        self.visibility_identifier_lookup_count
9375    }
9376
9377    #[cfg(any(test, feature = "test-support"))]
9378    pub fn visibility_identifier_batch_count(&self) -> usize {
9379        self.visibility_identifier_batch_count
9380    }
9381
9382    #[cfg(any(test, feature = "test-support"))]
9383    pub fn reset_target_preserving_type_resolution_count(&self) {
9384        self.target_preserving_type_resolution_count
9385            .store(0, Ordering::Relaxed);
9386    }
9387
9388    #[cfg(any(test, feature = "test-support"))]
9389    pub fn target_preserving_type_resolution_count(&self) -> usize {
9390        self.target_preserving_type_resolution_count
9391            .load(Ordering::Relaxed)
9392    }
9393
9394    #[cfg(any(test, feature = "test-support"))]
9395    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9396        self.visible_parser_alias_name_set_build_count
9397            .load(Ordering::Relaxed)
9398    }
9399}
9400
9401#[derive(Default)]
9402struct IncludeGraph {
9403    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9404}
9405
9406impl IncludeGraph {
9407    fn extend_with<F>(
9408        &mut self,
9409        root: &ProjectFile,
9410        cancellation: Option<&CancellationToken>,
9411        targets_for: &mut F,
9412    ) where
9413        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9414    {
9415        let mut stack = vec![root.clone()];
9416        while let Some(file) = stack.pop() {
9417            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9418                break;
9419            }
9420            if self.targets_by_file.contains_key(&file) {
9421                continue;
9422            }
9423            let targets = targets_for(&file);
9424            stack.extend(targets.iter().cloned());
9425            self.targets_by_file.insert(file, targets);
9426        }
9427    }
9428
9429    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9430        self.targets_by_file.keys()
9431    }
9432
9433    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9434        self.targets_by_file
9435            .get(file)
9436            .map(Vec::as_slice)
9437            .unwrap_or_default()
9438    }
9439
9440    fn reachable_files(
9441        &self,
9442        root: &ProjectFile,
9443        cancellation: Option<&CancellationToken>,
9444    ) -> HashSet<ProjectFile> {
9445        let mut pending = vec![root.clone()];
9446        let mut visited = HashSet::default();
9447        while let Some(file) = pending.pop() {
9448            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9449                break;
9450            }
9451            if visited.insert(file.clone()) {
9452                pending.extend(self.targets(&file).iter().cloned());
9453            }
9454        }
9455        visited
9456    }
9457}
9458
9459fn build_bounded_visible_declarations(
9460    cpp: &dyn CppSource,
9461    token: QueryToken<'_>,
9462    analyzer: &CppGraphSource<'_>,
9463    roots: &HashSet<ProjectFile>,
9464    visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9465    cancellation: Option<&CancellationToken>,
9466    stats: &mut BoundedVisibilityStats,
9467) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9468    let mut candidates_by_identifier = HashMap::default();
9469    let mut declarations_by_source_and_reading: HashMap<
9470        (ProjectFile, bool),
9471        BoundedVisibilityDeclarations,
9472    > = HashMap::default();
9473    let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9474    roots
9475        .iter()
9476        .map(|root| {
9477            let reading_is_c = analyzer.reference_uses_c_semantics(root);
9478            let root_declarations = declarations_by_source_and_reading
9479                .entry((root.clone(), reading_is_c))
9480                .or_insert_with(|| {
9481                    bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9482                });
9483            let mut visible = root_declarations
9484                .all
9485                .iter()
9486                .cloned()
9487                .collect::<HashSet<_>>();
9488            let mut pending_names = HashSet::default();
9489            if let Some(prepared) = cpp.prepared_syntax(token, root) {
9490                // One cursor for the whole file walk: `named_child(index)`
9491                // re-steps the sibling list from the first child on every
9492                // access, which made this whole-file scan quadratic in the
9493                // fan-out of each node (#3097).
9494                let mut cursor = prepared.tree().walk();
9495                let mut pending_nodes = vec![prepared.tree().root_node()];
9496                while let Some(node) = pending_nodes.pop() {
9497                    if matches!(
9498                        node.kind(),
9499                        "identifier"
9500                            | "type_identifier"
9501                            | "field_identifier"
9502                            | "namespace_identifier"
9503                    ) {
9504                        pending_names.insert(node_text(node, prepared.source()).to_string());
9505                    }
9506                    if node.kind() == "preproc_arg" {
9507                        for reference in
9508                            object_macro_replacement_type_references(node, prepared.source())
9509                        {
9510                            pending_names.extend(reference.components);
9511                        }
9512                    }
9513                    pending_nodes.extend(node.named_children(&mut cursor));
9514                }
9515            }
9516            stats.root_names += pending_names.len();
9517            let mut completed_names = HashSet::default();
9518            while !pending_names.is_empty() {
9519                stats.rounds += 1;
9520                let round_names = std::mem::take(&mut pending_names);
9521                let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9522                    HashMap::default();
9523                let mut identifiers = Vec::new();
9524                for identifier in round_names {
9525                    if !completed_names.insert(identifier.clone())
9526                        || cancellation.is_some_and(CancellationToken::is_cancelled)
9527                    {
9528                        continue;
9529                    }
9530                    identifiers.push(identifier);
9531                }
9532                let missing_identifiers = identifiers
9533                    .iter()
9534                    .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9535                    .cloned()
9536                    .collect::<HashSet<_>>();
9537                if !missing_identifiers.is_empty() {
9538                    let lookup_started = Instant::now();
9539                    let mut candidates = cpp
9540                        .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9541                    stats.lookup_elapsed += lookup_started.elapsed();
9542                    stats.identifier_lookups += missing_identifiers.len();
9543                    stats.identifier_batches += 1;
9544                    if cancellation.is_some_and(CancellationToken::is_cancelled) {
9545                        break;
9546                    }
9547                    for identifier in missing_identifiers {
9548                        let units = candidates.remove(&identifier).unwrap_or_default();
9549                        let candidate_count = units.len();
9550                        let candidate_sources = units
9551                            .into_iter()
9552                            .map(|unit| unit.source().clone())
9553                            .collect::<HashSet<_>>();
9554                        candidates_by_identifier
9555                            .insert(identifier, (candidate_sources, candidate_count));
9556                    }
9557                }
9558                for identifier in identifiers {
9559                    let (candidate_sources, candidate_count) = candidates_by_identifier
9560                        .get(&identifier)
9561                        .expect("every missing identifier was inserted after the batch lookup");
9562                    stats.candidate_units += *candidate_count;
9563                    for source in candidate_sources.iter().cloned() {
9564                        if source != *root
9565                            && visible_sources
9566                                .get(root)
9567                                .is_some_and(|files| files.contains(&source))
9568                        {
9569                            requested_names_by_source
9570                                .entry(source)
9571                                .or_default()
9572                                .insert(identifier.clone());
9573                        }
9574                    }
9575                }
9576                stats.candidate_sources += requested_names_by_source.len();
9577                for (source, requested_names) in requested_names_by_source {
9578                    let declarations = declarations_by_source_and_reading
9579                        .entry((source.clone(), reading_is_c))
9580                        .or_insert_with(|| {
9581                            bounded_visibility_declarations(
9582                                cpp,
9583                                analyzer,
9584                                &source,
9585                                reading_is_c,
9586                                stats,
9587                            )
9588                        });
9589                    let mut selected = HashSet::default();
9590                    for name in requested_names {
9591                        if let Some(units) = declarations.by_identifier.get(&name) {
9592                            selected.extend(units.iter().cloned());
9593                        }
9594                    }
9595                    for unit in selected {
9596                        stats.selected_units += 1;
9597                        let dependency_names = dependency_names_by_unit
9598                            .entry(unit.clone())
9599                            .or_insert_with(|| {
9600                                let mut dependency_names = HashSet::default();
9601                                if let Some(prepared) = cpp.prepared_syntax(token, &source) {
9602                                    let ast_started = Instant::now();
9603                                    let mut cursor = prepared.tree().walk();
9604                                    for range in analyzer.ranges(&unit) {
9605                                        let Some(declaration) = node_for_exact_range(
9606                                            prepared.tree().root_node(),
9607                                            &range,
9608                                        ) else {
9609                                            continue;
9610                                        };
9611                                        let mut pending_nodes = vec![declaration];
9612                                        while let Some(node) = pending_nodes.pop() {
9613                                            stats.dependency_ast_nodes += 1;
9614                                            #[cfg(any(test, feature = "test-support"))]
9615                                            BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
9616                                                .with(|count| count.set(count.get() + 1));
9617                                            if matches!(
9618                                                node.kind(),
9619                                                "type_identifier" | "namespace_identifier"
9620                                            ) {
9621                                                dependency_names.insert(
9622                                                    node_text(node, prepared.source()).into(),
9623                                                );
9624                                            }
9625                                            pending_nodes.extend(node.named_children(&mut cursor));
9626                                        }
9627                                    }
9628                                    stats.dependency_ast_elapsed += ast_started.elapsed();
9629                                }
9630                                dependency_names
9631                            });
9632                        for name in dependency_names.iter() {
9633                            if !completed_names.contains(name) && pending_names.insert(name.clone())
9634                            {
9635                                stats.dependency_names += 1;
9636                            }
9637                        }
9638                        visible.insert(unit);
9639                    }
9640                }
9641            }
9642            (root.clone(), visible)
9643        })
9644        .collect()
9645}
9646
9647struct BoundedVisibilityDeclarations {
9648    all: Vec<CodeUnit>,
9649    by_identifier: HashMap<String, Vec<CodeUnit>>,
9650}
9651
9652fn bounded_visibility_declarations(
9653    cpp: &dyn CppSource,
9654    analyzer: &CppGraphSource<'_>,
9655    file: &ProjectFile,
9656    c_semantics: bool,
9657    stats: &mut BoundedVisibilityStats,
9658) -> BoundedVisibilityDeclarations {
9659    let declarations_started = Instant::now();
9660    let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
9661        .into_iter()
9662        .collect::<Vec<_>>();
9663    stats.declaration_elapsed += declarations_started.elapsed();
9664    stats.declaration_reads += 1;
9665    stats.declaration_units += all.len();
9666
9667    let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9668    for unit in &all {
9669        by_identifier
9670            .entry(unit.identifier().to_string())
9671            .or_default()
9672            .push(unit.clone());
9673        if unit.is_class()
9674            && let Some(metadata) = cpp.template_metadata(unit)
9675            && metadata.primary_name != unit.identifier()
9676        {
9677            by_identifier
9678                .entry(metadata.primary_name)
9679                .or_default()
9680                .push(unit.clone());
9681        }
9682    }
9683    BoundedVisibilityDeclarations { all, by_identifier }
9684}
9685
9686#[derive(Default)]
9687struct BoundedVisibilityStats {
9688    rounds: usize,
9689    root_names: usize,
9690    identifier_lookups: usize,
9691    identifier_batches: usize,
9692    candidate_units: usize,
9693    candidate_sources: usize,
9694    declaration_reads: usize,
9695    declaration_units: usize,
9696    selected_units: usize,
9697    dependency_ast_nodes: usize,
9698    dependency_names: usize,
9699    lookup_elapsed: Duration,
9700    declaration_elapsed: Duration,
9701    dependency_ast_elapsed: Duration,
9702}
9703
9704fn bounded_visibility_declarations_in_reading(
9705    analyzer: &CppGraphSource<'_>,
9706    file: &ProjectFile,
9707    c_semantics: bool,
9708) -> BTreeSet<CodeUnit> {
9709    #[cfg(any(test, feature = "test-support"))]
9710    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9711    analyzer.declarations_in_reading(file, c_semantics)
9712}
9713
9714#[cfg(any(test, feature = "test-support"))]
9715pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9716    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9717}
9718
9719#[cfg(any(test, feature = "test-support"))]
9720pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9721    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9722}
9723
9724#[cfg(any(test, feature = "test-support"))]
9725pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
9726    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
9727}
9728
9729#[cfg(any(test, feature = "test-support"))]
9730pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
9731    BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
9732}
9733
9734pub struct VisibilityData {
9735    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9736    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9737}
9738
9739/// Build the per-root include closure and the declarations each root can see
9740/// through it.
9741///
9742/// `declarations_for` takes the reading to answer in (issue #1970): a root
9743/// compiled as C sees the C reading of every file in its closure, a root
9744/// compiled as C++ sees the C++ reading, and `reading_is_c_for` decides which
9745/// per root. The two readings agree for all but a handful of headers, so the
9746/// C map is built only when some root actually asks for it, and only over the
9747/// files that root reaches.
9748pub fn build_visibility_data<F, R, D>(
9749    roots: &HashSet<ProjectFile>,
9750    cancellation: Option<&CancellationToken>,
9751    mut targets_for: F,
9752    mut reading_is_c_for: R,
9753    mut declarations_for: D,
9754) -> VisibilityData
9755where
9756    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9757    R: FnMut(&ProjectFile) -> bool,
9758    D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9759{
9760    let mut include_graph = IncludeGraph::default();
9761    for file in roots {
9762        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9763            break;
9764        }
9765        include_graph.extend_with(file, cancellation, &mut targets_for);
9766    }
9767    let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9768        .files()
9769        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9770        .map(|file| (file.clone(), declarations_for(file, false)))
9771        .collect();
9772    let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9773    let mut visible_by_file = HashMap::default();
9774    let mut visible_source_files_by_root = HashMap::default();
9775    for file in roots {
9776        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9777            break;
9778        }
9779        let mut visited = HashSet::default();
9780        let mut visible = HashSet::default();
9781        let declarations_by_file = if reading_is_c_for(file) {
9782            for reached in cpp_declarations_by_file.keys() {
9783                if !c_declarations_by_file.contains_key(reached) {
9784                    let declarations = declarations_for(reached, true);
9785                    c_declarations_by_file.insert(reached.clone(), declarations);
9786                }
9787            }
9788            &c_declarations_by_file
9789        } else {
9790            &cpp_declarations_by_file
9791        };
9792        collect_visible_declarations(
9793            &include_graph,
9794            declarations_by_file,
9795            file,
9796            &mut visited,
9797            &mut visible,
9798            cancellation,
9799        );
9800        visible_by_file.insert(file.clone(), visible);
9801        visible_source_files_by_root.insert(file.clone(), visited);
9802    }
9803    VisibilityData {
9804        visible_by_file,
9805        visible_source_files_by_root,
9806    }
9807}
9808
9809/// Admit the class that an out-of-line definition proves is in scope.
9810///
9811/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
9812/// names a class-like entity in that file's scope: a member declaration can
9813/// live in a file other than its class's only when it is written out of line.
9814/// A file a build concatenates rather than compiles carries no `#include` edge
9815/// to the header declaring `Owner` -- google/wuffs
9816/// `internal/cgen/auxiliary/image.cc` defines
9817/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
9818/// every unqualified member and constructor reference in it had no candidate at
9819/// all (#1832).
9820///
9821/// The evidence is the indexed declaration's own owner name, taken from its
9822/// `FqName`, so this stays a structured answer rather than a text fallback.
9823/// Only an owner the file cannot already see is admitted: that is what keeps a
9824/// header declaring its own class from additionally seeing every same-named
9825/// class in the workspace, and it makes the pass free for the ordinary file
9826/// whose owners are all visible.
9827#[derive(Default)]
9828struct OutOfLineOwnerBindingStats {
9829    unseen_owners: usize,
9830    definition_lookups: usize,
9831    admitted: usize,
9832}
9833
9834fn extend_with_out_of_line_owner_bindings(
9835    cpp: &dyn CppSource,
9836    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9837) -> OutOfLineOwnerBindingStats {
9838    let mut stats = OutOfLineOwnerBindingStats::default();
9839    for (file, visible) in visible_by_file.iter_mut() {
9840        // The include-closure walk seeds every root with its own declarations,
9841        // so the file's members are already here; re-reading them from the
9842        // analyzer would pay for the same declaration set twice.
9843        let mut unseen_owners: HashSet<String> = visible
9844            .iter()
9845            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9846            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9847            .collect();
9848        if unseen_owners.is_empty() {
9849            continue;
9850        }
9851        for unit in visible.iter().filter(|unit| unit.is_class()) {
9852            unseen_owners.remove(&unit.fq_name());
9853        }
9854        stats.unseen_owners += unseen_owners.len();
9855        stats.definition_lookups += unseen_owners.len();
9856        let admitted = unseen_owners
9857            .iter()
9858            .flat_map(|owner| cpp.definitions(owner))
9859            .filter(CodeUnit::is_class)
9860            .collect::<Vec<_>>();
9861        stats.admitted += admitted.len();
9862        visible.extend(admitted);
9863    }
9864    stats
9865}
9866
9867pub enum VisibleMemberResolution {
9868    Callable(Vec<CodeUnit>),
9869    NonCallable,
9870    AmbiguousKind,
9871    Missing,
9872}
9873
9874#[derive(Clone)]
9875pub enum EnclosingMemberOwnerResolution {
9876    Owner(CodeUnit),
9877    Ambiguous,
9878    Missing,
9879}
9880
9881pub fn resolve_declaring_member_owner(
9882    analyzer: &CppGraphSource<'_>,
9883    visibility: &VisibilityIndex<'_>,
9884    file: &ProjectFile,
9885    receiver_owner: &CodeUnit,
9886    member_name: &str,
9887) -> EnclosingMemberOwnerResolution {
9888    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9889        return EnclosingMemberOwnerResolution::Missing;
9890    };
9891    let Some(receiver_owner) =
9892        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9893    else {
9894        return EnclosingMemberOwnerResolution::Ambiguous;
9895    };
9896    let resolve_level = |frontier: &[CodeUnit]| {
9897        let mut member_owners = Vec::new();
9898        for raw_owner in frontier {
9899            let Some(owner) =
9900                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9901            else {
9902                return EnclosingMemberOwnerResolution::Ambiguous;
9903            };
9904            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9905                // A type nested in an owner shares the owner's member-name
9906                // index, but it cannot be the value receiver of `owner.name`.
9907                // Keep value members here so an anonymous aggregate field
9908                // does not become ambiguous with its promoted receiver type.
9909                if !member.is_field() && !member.is_function() {
9910                    continue;
9911                }
9912                let Some(member_owner) = type_owner_of(analyzer, member) else {
9913                    return EnclosingMemberOwnerResolution::Ambiguous;
9914                };
9915                if !member_owners
9916                    .iter()
9917                    .any(|existing| same_visible_symbol(existing, &member_owner))
9918                {
9919                    member_owners.push(member_owner);
9920                }
9921            }
9922        }
9923        match member_owners.len() {
9924            0 => EnclosingMemberOwnerResolution::Missing,
9925            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9926            _ => EnclosingMemberOwnerResolution::Ambiguous,
9927        }
9928    };
9929    // The first declaration on each structured base path hides deeper names,
9930    // regardless of whether its callable overload is applicable at a particular
9931    // call site. Applicability is checked only after this owner is established.
9932    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9933    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9934        return direct;
9935    }
9936    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9937    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9938    let mut path_matches = Vec::new();
9939    while let Some(raw_owner) = stack.pop() {
9940        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9941        else {
9942            return EnclosingMemberOwnerResolution::Ambiguous;
9943        };
9944        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
9945        // Propagate at most two occurrences of each owner: that preserves the distinction
9946        // between one and multiple resolving base paths without exponential diamond walks.
9947        let propagated = propagated_counts.entry(owner.clone()).or_default();
9948        if *propagated == 2 {
9949            continue;
9950        }
9951        *propagated += 1;
9952        match resolve_level(std::slice::from_ref(&owner)) {
9953            EnclosingMemberOwnerResolution::Owner(owner) => {
9954                path_matches.push(owner);
9955                if path_matches.len() == 2 {
9956                    return EnclosingMemberOwnerResolution::Ambiguous;
9957                }
9958            }
9959            EnclosingMemberOwnerResolution::Ambiguous => {
9960                return EnclosingMemberOwnerResolution::Ambiguous;
9961            }
9962            EnclosingMemberOwnerResolution::Missing => {
9963                stack.extend(hierarchy.get_direct_ancestors(&owner));
9964            }
9965        }
9966    }
9967    match path_matches.len() {
9968        0 => EnclosingMemberOwnerResolution::Missing,
9969        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
9970        _ => unreachable!("base-path matches are capped at one before returning"),
9971    }
9972}
9973
9974/// Resolve the declaring owner of a callable after applying a member
9975/// `using <Base>::<member>;` declaration to one exact call arity.
9976///
9977/// Ordinary member lookup is intentionally name-based: the first class that
9978/// declares a name hides the same name on deeper bases. A member
9979/// using-declaration is the one exception. When none of the declarations on
9980/// that first owner accepts the call arity, it can reintroduce an applicable
9981/// overload from the named base. If a declaration on the first owner does
9982/// accept the arity, argument types would be needed to choose between it and
9983/// a same-arity introduced overload, so this resolver conservatively keeps the
9984/// ordinary owner (#1835/#1843).
9985///
9986/// The caller supplies ordinary name-based owner resolution so a file scan can
9987/// reuse its existing owner cache before applying this callable-only exception.
9988pub fn resolve_declaring_callable_owner(
9989    analyzer: &CppGraphSource<'_>,
9990    visibility: &VisibilityIndex<'_>,
9991    file: &ProjectFile,
9992    ordinary: EnclosingMemberOwnerResolution,
9993    member_name: &str,
9994    call_arity: usize,
9995) -> EnclosingMemberOwnerResolution {
9996    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
9997        return ordinary;
9998    };
9999    if visibility
10000        .visible_members_for_owner_name(file, ordinary_owner, member_name)
10001        .into_iter()
10002        .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
10003    {
10004        return ordinary;
10005    }
10006
10007    let mut pending = match member_using_declaration_bases(
10008        analyzer,
10009        visibility,
10010        file,
10011        ordinary_owner,
10012        member_name,
10013    ) {
10014        Ok(bases) => bases,
10015        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10016    };
10017    let mut visited = HashSet::default();
10018    let mut introduced_owners = Vec::new();
10019    while let Some(owner) = pending.pop() {
10020        if !visited.insert(owner.clone()) {
10021            continue;
10022        }
10023        let accepts_arity = visibility
10024            .visible_members_for_owner_name(file, &owner, member_name)
10025            .into_iter()
10026            .any(|unit| {
10027                unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
10028            });
10029        if accepts_arity {
10030            if !introduced_owners
10031                .iter()
10032                .any(|existing| same_visible_symbol(existing, &owner))
10033            {
10034                introduced_owners.push(owner);
10035            }
10036            continue;
10037        }
10038        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10039            Ok(bases) => pending.extend(bases),
10040            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10041        }
10042    }
10043    match introduced_owners.as_slice() {
10044        [] => ordinary,
10045        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10046        _ => EnclosingMemberOwnerResolution::Ambiguous,
10047    }
10048}
10049
10050fn member_using_declaration_bases(
10051    analyzer: &CppGraphSource<'_>,
10052    visibility: &VisibilityIndex<'_>,
10053    file: &ProjectFile,
10054    owner: &CodeUnit,
10055    member_name: &str,
10056) -> Result<Vec<CodeUnit>, ()> {
10057    let Some(source) = analyzer.get_source(owner, false) else {
10058        return Ok(Vec::new());
10059    };
10060    let scopes = cpp_member_using_declaration_scopes(&source, member_name);
10061    if scopes.is_empty() {
10062        return Ok(Vec::new());
10063    }
10064    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10065        return Ok(Vec::new());
10066    };
10067    let mut bases = Vec::new();
10068    for raw_ancestor in hierarchy.get_ancestors(owner) {
10069        let Some(ancestor) =
10070            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10071        else {
10072            return Err(());
10073        };
10074        let qualified = cpp_name_for(&ancestor);
10075        if scopes
10076            .iter()
10077            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10078            && !bases
10079                .iter()
10080                .any(|existing| same_visible_symbol(existing, &ancestor))
10081        {
10082            bases.push(ancestor);
10083        }
10084    }
10085    Ok(bases)
10086}
10087
10088pub fn lexical_component_tiers<'a>(
10089    components: &'a [String],
10090    global: bool,
10091    lexical_scope: &'a [String],
10092) -> impl Iterator<Item = Vec<String>> + 'a {
10093    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10094    (0..=first_prefix_len).rev().map(move |prefix_len| {
10095        let mut qualified = Vec::with_capacity(prefix_len + components.len());
10096        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10097        qualified.extend_from_slice(components);
10098        qualified
10099    })
10100}
10101
10102pub fn build_visible_identifier_index(
10103    analyzer: &CppGraphSource<'_>,
10104    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10105    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10106    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10107) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10108    let mut out = HashMap::default();
10109    for (file, visible) in visible_by_file {
10110        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10111        for unit in visible {
10112            if unit.is_field()
10113                && !visible_source_files_by_root
10114                    .get(file)
10115                    .is_some_and(|sources| sources.contains(unit.source()))
10116                && cpp_global_field_has_internal_linkage_cached(
10117                    analyzer,
10118                    global_field_internal_linkage,
10119                    unit,
10120                )
10121            {
10122                continue;
10123            }
10124            by_identifier
10125                .entry(unit.identifier().to_string())
10126                .or_default()
10127                .push(unit.clone());
10128        }
10129        for units in by_identifier.values_mut() {
10130            sort_lookup_units(units);
10131            units.dedup();
10132        }
10133        out.insert(file.clone(), by_identifier);
10134    }
10135    out
10136}
10137
10138fn sort_lookup_units(units: &mut [CodeUnit]) {
10139    units.sort_by(|left, right| {
10140        left.fq_name()
10141            .cmp(&right.fq_name())
10142            .then_with(|| left.signature().cmp(&right.signature()))
10143            .then_with(|| left.source().cmp(right.source()))
10144            .then_with(|| left.kind().cmp(&right.kind()))
10145            .then_with(|| {
10146                left.package_segment_count()
10147                    .cmp(&right.package_segment_count())
10148            })
10149            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10150            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10151    });
10152}
10153
10154fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10155    let interner = segment_interner();
10156    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10157        let (left_text, left_kind) = interner.resolve(left_id);
10158        let (right_text, right_kind) = interner.resolve(right_id);
10159        let order = left_text
10160            .cmp(right_text)
10161            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10162        if order != CmpOrdering::Equal {
10163            return order;
10164        }
10165    }
10166    left.len().cmp(&right.len())
10167}
10168
10169const fn segment_kind_order(kind: SegmentKind) -> u8 {
10170    match kind {
10171        SegmentKind::Path => 0,
10172        SegmentKind::Package => 1,
10173        SegmentKind::Type => 2,
10174        SegmentKind::Companion => 3,
10175        SegmentKind::Nested => 4,
10176        SegmentKind::Member => 5,
10177        SegmentKind::Unknown => 6,
10178    }
10179}
10180
10181fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10182    let mut deduped = Vec::with_capacity(units.len());
10183    for unit in units.drain(..) {
10184        if !deduped.contains(&unit) {
10185            deduped.push(unit);
10186        }
10187    }
10188    *units = deduped;
10189}
10190
10191pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10192    // Same domain as `candidate_units` above: `reference` is a plain
10193    // `::`-joined qualified-id with operator tokens kept intact by the shared
10194    // splitter's operator merge.
10195    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10196        brokk_bifrost_core::analyzer::Language::Cpp,
10197        reference,
10198    );
10199    if parts.is_empty() {
10200        return Vec::new();
10201    }
10202
10203    let mut candidates = Vec::new();
10204    for package_len in 0..parts.len() {
10205        let package = parts[..package_len].join("::");
10206        let rest = &parts[package_len..];
10207        if rest.is_empty() {
10208            continue;
10209        }
10210        match kind {
10211            TargetKind::Type | TargetKind::Constructor => {
10212                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10213                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10214            }
10215            TargetKind::FreeFunction
10216            | TargetKind::Method
10217            | TargetKind::GlobalField
10218            | TargetKind::MemberField
10219            | TargetKind::Macro => {
10220                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10221                if rest.len() > 1 {
10222                    let owner = rest[..rest.len() - 1].join("$");
10223                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10224                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
10225                }
10226            }
10227        }
10228    }
10229    candidates
10230}
10231
10232fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10233    let fqn = if package.is_empty() {
10234        short.to_string()
10235    } else {
10236        format!("{package}.{short}")
10237    };
10238    if !out.contains(&fqn) {
10239        out.push(fqn);
10240    }
10241}
10242
10243pub fn infer_cpp_initializer_type(
10244    analyzer: &CppGraphSource<'_>,
10245    visibility: &VisibilityIndex<'_>,
10246    file: &ProjectFile,
10247    source: &str,
10248    node: Node<'_>,
10249) -> Option<CodeUnit> {
10250    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10251        .and_then(|binding| binding.unit)
10252}
10253
10254pub fn infer_cpp_initializer_binding(
10255    analyzer: &CppGraphSource<'_>,
10256    visibility: &VisibilityIndex<'_>,
10257    file: &ProjectFile,
10258    source: &str,
10259    node: Node<'_>,
10260    receiver_resolver: Option<&ReceiverResolver<'_>>,
10261) -> Option<CppScanBinding> {
10262    match node.kind() {
10263        "new_expression" => {
10264            let text = normalize_cpp_whitespace(node_text(node, source));
10265            let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
10266            let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
10267            let name = normalize_cpp_type_name(type_text);
10268            Some(CppScanBinding::from_type_name(
10269                name.clone(),
10270                visibility.resolve_type(file, &name),
10271                1,
10272            ))
10273        }
10274        "call_expression" => node.child_by_field_name("function").and_then(|function| {
10275            // `a().b()` and `p->b()` invoke a member on a receiver *value*. The
10276            // callee's source text is an expression, not a name, and every name
10277            // lookup below normalizes a reference by truncating at the first
10278            // `(`: `first().second` would read as `first`, so the chained call
10279            // would take the type of `first()` instead of the type of
10280            // `first().second()` (#2178). Only the member path can answer for
10281            // this shape, so route to it from the callee's node kind.
10282            if function.kind() == "field_expression" {
10283                let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10284                return resolve_field_method_call_return_binding(
10285                    analyzer,
10286                    visibility,
10287                    file,
10288                    source,
10289                    function,
10290                    arity,
10291                    receiver_resolver,
10292                );
10293            }
10294            let function_text = node_text(function, source);
10295            let direct_type_binding = visibility
10296                .resolve_type(file, function_text)
10297                .map(|unit| CppScanBinding::from_unit(unit, 0));
10298            if function.kind() == "template_function" && direct_type_binding.is_some() {
10299                let lexical_namespace = enclosing_namespace_context(node, source);
10300                let arity = visibility.call_arity_evidence(file, node, source).exact();
10301                if let Some(arity) = arity
10302                    && let Some(binding) = visibility.resolve_call_return_binding(
10303                        analyzer,
10304                        file,
10305                        function_text,
10306                        arity,
10307                        lexical_namespace.as_deref(),
10308                        direct_type_binding
10309                            .as_ref()
10310                            .and_then(|binding| binding.unit.as_ref()),
10311                    )
10312                {
10313                    return Some(binding);
10314                }
10315                let (has_callable, callable_binding) = visibility
10316                    .resolve_call_return_binding_without_arity(
10317                        analyzer,
10318                        file,
10319                        function_text,
10320                        lexical_namespace.as_deref(),
10321                        direct_type_binding
10322                            .as_ref()
10323                            .and_then(|binding| binding.unit.as_ref()),
10324                    );
10325                if let Some(binding) = callable_binding {
10326                    return Some(binding);
10327                }
10328                if has_callable {
10329                    return None;
10330                }
10331                return direct_type_binding;
10332            }
10333            // Only the return-typed branches need the argument count. An
10334            // unknown arity leaves them out, exactly as in the template arm
10335            // above, and still constructs the direct type: `File(getPath())`
10336            // names `File` whether or not `getPath()`'s expansion is provable.
10337            let arity = visibility.call_arity_evidence(file, node, source).exact();
10338            if let Some(arity) = arity {
10339                let direct_type_binding_for_call = direct_type_binding.clone();
10340                if let Some(binding) = resolve_static_method_call_return_binding(
10341                    analyzer, visibility, file, source, function, arity,
10342                )
10343                .or_else(|| {
10344                    // An applicable free function supplies the receiver value
10345                    // before an unrelated visible type with the same terminal
10346                    // name. The direct type still excludes its own constructor
10347                    // declaration below and remains the construction fallback.
10348                    visibility.resolve_call_return_binding(
10349                        analyzer,
10350                        file,
10351                        function_text,
10352                        arity,
10353                        enclosing_namespace_context(node, source).as_deref(),
10354                        direct_type_binding_for_call
10355                            .as_ref()
10356                            .and_then(|binding| binding.unit.as_ref()),
10357                    )
10358                }) {
10359                    return Some(binding);
10360                }
10361            }
10362            direct_type_binding
10363        }),
10364        _ => None,
10365    }
10366}
10367
10368fn resolve_static_method_call_return_binding(
10369    analyzer: &CppGraphSource<'_>,
10370    visibility: &VisibilityIndex<'_>,
10371    file: &ProjectFile,
10372    source: &str,
10373    function: Node<'_>,
10374    arity: usize,
10375) -> Option<CppScanBinding> {
10376    if function.kind() != "qualified_identifier" {
10377        return None;
10378    }
10379    let qualified = normalize_cpp_reference_text(node_text(function, source));
10380    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
10381    // single component (the shared splitter's operator-token merge keeps
10382    // `operator+`-style names intact), so re-tokenizing with the shared
10383    // structured splitter and peeling the terminal segment reproduces
10384    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
10385    // `cpp_out_of_line_function_owner`'s `qualified` split above.
10386    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10387        brokk_bifrost_core::analyzer::Language::Cpp,
10388        &qualified,
10389    );
10390    let (owner_text, member_name) = match parts.split_last() {
10391        Some((member, owner_parts)) if !owner_parts.is_empty() => {
10392            (owner_parts.join("::"), member.clone())
10393        }
10394        _ => {
10395            let scope = function.child_by_field_name("scope")?;
10396            let name = function.child_by_field_name("name")?;
10397            (
10398                node_text(scope, source).to_string(),
10399                node_text(name, source).to_string(),
10400            )
10401        }
10402    };
10403    let owner = visibility.resolve_type(file, &owner_text)?;
10404    let candidates = visibility
10405        .visible_members_for_owner_name(file, &owner, &member_name)
10406        .into_iter()
10407        .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
10408        .cloned()
10409        .collect::<Vec<_>>();
10410    unanimous_return_binding(analyzer, visibility, file, &candidates)
10411}
10412
10413fn resolve_field_method_call_return_binding(
10414    analyzer: &CppGraphSource<'_>,
10415    visibility: &VisibilityIndex<'_>,
10416    file: &ProjectFile,
10417    source: &str,
10418    function: Node<'_>,
10419    arity: usize,
10420    receiver_resolver: Option<&ReceiverResolver<'_>>,
10421) -> Option<CppScanBinding> {
10422    debug_assert_eq!(
10423        function.kind(),
10424        "field_expression",
10425        "the member-call return binding answers only for a field-expression callee"
10426    );
10427    let receiver_resolver = receiver_resolver?;
10428    let field = function.child_by_field_name("field")?;
10429    let member_name = node_text(function_terminal_node(field), source);
10430    let receiver = function
10431        .child_by_field_name("argument")
10432        .or_else(|| function.named_child(0))?;
10433    let owners = receiver_resolver(receiver, source);
10434    let mut candidates = Vec::new();
10435    for owner in owners {
10436        let declaring_owner =
10437            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10438                EnclosingMemberOwnerResolution::Owner(owner) => owner,
10439                EnclosingMemberOwnerResolution::Missing => continue,
10440                EnclosingMemberOwnerResolution::Ambiguous => return None,
10441            };
10442        candidates.extend(
10443            visibility
10444                .visible_members_for_owner_name(file, &declaring_owner, member_name)
10445                .into_iter()
10446                .filter(|unit| {
10447                    unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
10448                })
10449                .cloned(),
10450        );
10451    }
10452    unanimous_return_binding(analyzer, visibility, file, &candidates)
10453}
10454
10455fn unanimous_return_binding(
10456    analyzer: &CppGraphSource<'_>,
10457    visibility: &VisibilityIndex<'_>,
10458    file: &ProjectFile,
10459    candidates: &[CodeUnit],
10460) -> Option<CppScanBinding> {
10461    let mut resolved_return: Option<CppScanBinding> = None;
10462    for function in candidates {
10463        let metadata = analyzer.signature_metadata(function);
10464        let return_types = if metadata.is_empty() {
10465            vec![cpp_function_return_type_text(analyzer, function)?]
10466        } else {
10467            metadata
10468                .iter()
10469                .map(|metadata| metadata.return_type_text().map(str::to_string))
10470                .collect::<Option<Vec<_>>>()?
10471        };
10472        for return_text in return_types {
10473            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10474            let name = normalize_cpp_type_name(&return_text);
10475            let binding = CppScanBinding::from_type_name(
10476                name.clone(),
10477                visibility
10478                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10479                indirection,
10480            );
10481            if let Some(existing) = resolved_return.as_ref()
10482                && (existing.indirection != binding.indirection
10483                    || match (&existing.unit, &binding.unit) {
10484                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
10485                        (None, None) => existing.type_name != binding.type_name,
10486                        (Some(_), None) | (None, Some(_)) => true,
10487                    })
10488            {
10489                return None;
10490            }
10491            resolved_return = Some(binding);
10492        }
10493    }
10494    resolved_return
10495}
10496
10497fn aliases_from_prepared_source(
10498    cpp: &dyn CppSource,
10499    token: QueryToken<'_>,
10500    file: &ProjectFile,
10501) -> Vec<CppAlias> {
10502    let Some(prepared) = cpp.prepared_syntax(token, file) else {
10503        return Vec::new();
10504    };
10505    let mut aliases = Vec::new();
10506    collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
10507    aliases
10508}
10509
10510fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
10511    walk_named_tree_preorder(root, true, |node| {
10512        match node.kind() {
10513            "alias_declaration" if alias_has_visible_file_scope(node) => {
10514                if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
10515                    out.push(alias);
10516                }
10517            }
10518            "type_definition" if alias_has_visible_file_scope(node) => {
10519                collect_typedef_aliases(node, source, out)
10520            }
10521            _ => {}
10522        }
10523        WalkControl::Continue
10524    });
10525}
10526
10527fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10528    let mut current = node.parent();
10529    while let Some(parent) = current {
10530        match parent.kind() {
10531            "translation_unit"
10532            | "namespace_definition"
10533            | "declaration_list"
10534            | "linkage_specification" => current = parent.parent(),
10535            "template_declaration" => current = parent.parent(),
10536            _ => return false,
10537        }
10538    }
10539    true
10540}
10541
10542fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
10543    let name = node
10544        .child_by_field_name("name")
10545        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
10546    let target = node
10547        .child_by_field_name("type")
10548        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
10549    Some(CppAlias {
10550        name,
10551        target,
10552        namespace: enclosing_namespace_context(node, source),
10553    })
10554}
10555
10556fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
10557    let Some(type_node) = node.child_by_field_name("type") else {
10558        return;
10559    };
10560    let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
10561        return;
10562    };
10563
10564    let mut cursor = node.walk();
10565    for child in node.named_children(&mut cursor) {
10566        if same_node(child, type_node) {
10567            continue;
10568        }
10569        if let Some(name) = extract_typedef_declarator_name(child, source) {
10570            out.push(CppAlias {
10571                name,
10572                target: target.clone(),
10573                namespace: enclosing_namespace_context(node, source),
10574            });
10575        }
10576    }
10577}
10578
10579fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
10580    match node.kind() {
10581        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
10582            normalize_reference_name(node_text(node, source))
10583        }
10584        _ => node
10585            .child_by_field_name("declarator")
10586            .or_else(|| node.child_by_field_name("name"))
10587            .or_else(|| last_named_child(node))
10588            .and_then(|child| extract_typedef_declarator_name(child, source)),
10589    }
10590}
10591
10592fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
10593    let count = node.named_child_count();
10594    if count == 0 {
10595        None
10596    } else {
10597        node.named_child(count - 1)
10598    }
10599}
10600
10601pub fn collect_include_closure(
10602    analyzer: &CppGraphSource<'_>,
10603    include_targets: &IncludeTargetIndex,
10604    file: &ProjectFile,
10605    out: &mut HashSet<ProjectFile>,
10606    cancellation: Option<&CancellationToken>,
10607) {
10608    let mut stack = vec![file.clone()];
10609    while let Some(file) = stack.pop() {
10610        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10611            break;
10612        }
10613        if !out.insert(file.clone()) {
10614            continue;
10615        }
10616        let imports = analyzer.import_statements(&file);
10617        for include in cpp_include_paths(&imports) {
10618            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10619                stack.push(target);
10620            }
10621        }
10622    }
10623}
10624
10625fn collect_visible_declarations(
10626    include_graph: &IncludeGraph,
10627    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10628    file: &ProjectFile,
10629    visited: &mut HashSet<ProjectFile>,
10630    out: &mut HashSet<CodeUnit>,
10631    cancellation: Option<&CancellationToken>,
10632) {
10633    let mut stack = vec![file.clone()];
10634    while let Some(file) = stack.pop() {
10635        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10636            break;
10637        }
10638        if !visited.insert(file.clone()) {
10639            continue;
10640        }
10641        if let Some(declarations) = declarations_by_file.get(&file) {
10642            out.extend(declarations.iter().cloned());
10643        }
10644        stack.extend(include_graph.targets(&file).iter().cloned());
10645    }
10646}
10647
10648pub fn signature_arity(signature: Option<&str>) -> usize {
10649    let Some(signature) = signature else {
10650        return 0;
10651    };
10652    let inner = signature
10653        .find('(')
10654        .and_then(|open| {
10655            signature[open + 1..]
10656                .find(')')
10657                .map(|close| &signature[open + 1..open + 1 + close])
10658        })
10659        .unwrap_or(signature)
10660        .trim();
10661    if inner.is_empty() || inner == "void" {
10662        return 0;
10663    }
10664    cpp_split_top_level_commas(inner).count()
10665}
10666
10667fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10668    let source = format!("void __bifrost_macro_parameters({replacement});");
10669    let mut parser = Parser::new();
10670    parser
10671        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10672        .ok()?;
10673    let tree = parser.parse(&source, None)?;
10674    let root = tree.root_node();
10675    if root.has_error() {
10676        return None;
10677    }
10678    let declaration = root.named_child(0)?;
10679    let declarator = declaration.child_by_field_name("declarator")?;
10680    let parameters = declarator.child_by_field_name("parameters")?;
10681    let mut required = 0;
10682    let mut total = 0;
10683    let mut repeated = false;
10684    let mut cursor = parameters.walk();
10685    for parameter in parameters.children(&mut cursor) {
10686        match parameter.kind() {
10687            "parameter_declaration" => {
10688                if parameter.child_by_field_name("declarator").is_none()
10689                    && parameter
10690                        .child_by_field_name("type")
10691                        .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
10692                {
10693                    continue;
10694                }
10695                required += 1;
10696                total += 1;
10697            }
10698            "optional_parameter_declaration" => total += 1,
10699            "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
10700                repeated = true;
10701            }
10702            _ => {}
10703        }
10704    }
10705    Some(CallableArity::new(required, total, repeated))
10706}
10707
10708pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
10709    analyzer
10710        .signature_metadata(unit)
10711        .into_iter()
10712        .find_map(|metadata| metadata.callable_arity())
10713        .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
10714}
10715
10716pub fn cpp_callable_parameter_types(
10717    analyzer: &CppGraphSource<'_>,
10718    unit: &CodeUnit,
10719) -> Option<Vec<String>> {
10720    analyzer
10721        .signature_metadata(unit)
10722        .into_iter()
10723        .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
10724        .or_else(|| unit.signature().and_then(cpp_signature_param_types))
10725}
10726
10727fn merge_compatible_callable_arities(
10728    left: CallableArity,
10729    right: CallableArity,
10730) -> Option<CallableArity> {
10731    let total = left.total();
10732    let left_repeated = left.accepts(total.saturating_add(1));
10733    let right_repeated = right.accepts(right.total().saturating_add(1));
10734    if total != right.total() || left_repeated != right_repeated {
10735        return None;
10736    }
10737    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10738    Some(CallableArity::new(required, total, left_repeated))
10739}
10740
10741fn find_include_activation(
10742    cpp: &dyn CppSource,
10743    token: QueryToken<'_>,
10744    file: &ProjectFile,
10745    prepared: &PreparedSyntaxTree,
10746    donor_source: &ProjectFile,
10747) -> Option<usize> {
10748    let include_targets = cpp.include_target_index();
10749    let mut direct_includes = Vec::new();
10750    let mut nodes = vec![prepared.tree().root_node()];
10751    // An include activates for the whole file, so only an unconditional
10752    // directive counts here.
10753    let reference = CallableReferenceContext {
10754        file,
10755        position: None,
10756    };
10757    while let Some(node) = nodes.pop() {
10758        if node.kind() == "preproc_include" {
10759            if callable_preprocessor_context_is_visible_for_reference(
10760                node,
10761                prepared.source(),
10762                &reference,
10763            ) {
10764                let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10765                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10766                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10767                        file,
10768                        &include,
10769                        include_targets,
10770                    )) {
10771                        direct_includes.push((node.end_byte(), target));
10772                    }
10773                }
10774            }
10775            continue;
10776        }
10777        push_named_children_reversed(node, &mut nodes);
10778    }
10779    direct_includes.sort_by_key(|(activation, _)| *activation);
10780    let mut known_missing = HashSet::default();
10781    direct_includes
10782        .into_iter()
10783        .find(|(_, direct)| {
10784            unconditional_include_reaches(
10785                cpp,
10786                token,
10787                include_targets,
10788                direct,
10789                donor_source,
10790                file,
10791                &mut known_missing,
10792            )
10793        })
10794        .map(|(activation, _)| activation)
10795}
10796
10797fn find_conditional_include_projection_index(
10798    cpp: &dyn CppSource,
10799    token: QueryToken<'_>,
10800    file: &ProjectFile,
10801    prepared: &PreparedSyntaxTree,
10802    on_state: &dyn Fn(),
10803) -> ConditionalIncludeProjectionIndex {
10804    let reference_is_c = reference_uses_c_semantics(cpp, file);
10805    let include_targets = cpp.include_target_index();
10806    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10807        HashMap::default();
10808    let mut pending = Vec::new();
10809    let mut nodes = vec![prepared.tree().root_node()];
10810    while let Some(node) = nodes.pop() {
10811        if node.kind() == "preproc_include" {
10812            let Some(required_guards) =
10813                include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10814            else {
10815                continue;
10816            };
10817            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10818            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10819                let Some(target) = unique_include_target(resolve_include_targets_with_index(
10820                    file,
10821                    &include,
10822                    include_targets,
10823                )) else {
10824                    continue;
10825                };
10826                pending.push((target, node.end_byte(), required_guards.clone()));
10827            }
10828            continue;
10829        }
10830        push_named_children_reversed(node, &mut nodes);
10831    }
10832
10833    // One reached file can have several distinct compatible guard paths. Each
10834    // (file, activation byte) key keeps only the inclusion-minimal guard sets:
10835    // the consumers ask existence questions whose answers are monotone in the
10836    // guard set -- a path whose requirements hold, stay stable, and stay
10837    // compatible under one environment does so under every subset as well --
10838    // so a state subsumed by an existing subset cannot witness anything its
10839    // subset does not, and inserting a smaller set evicts the supersets it
10840    // subsumes. Exact-set dedup still terminated cycles, but dense `#ifdef`
10841    // lattices (QMK's per-keyboard feature guards) enumerated the powerset of
10842    // path-union guard sets through it: the state space, the per-key linear
10843    // scans, and resident memory all grew without bound (#2365).
10844    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10845        HashMap::default();
10846    while let Some((current_file, activation_byte, path)) = pending.pop() {
10847        let guard_sets = expanded
10848            .entry((current_file.clone(), activation_byte))
10849            .or_default();
10850        // Minimality is keyed on the full guard set alone: a path's undecided
10851        // guards are the per-conditional subset of that set, so a subset path
10852        // carries a subset of them too.
10853        if guard_sets
10854            .iter()
10855            .any(|existing| existing.is_subset(&path.all))
10856        {
10857            continue;
10858        }
10859        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10860            .drain(..)
10861            .partition(|existing| path.all.is_subset(existing));
10862        *guard_sets = kept;
10863        guard_sets.push(path.all.clone());
10864        if !evicted.is_empty()
10865            && let Some(projections) = projections_by_source.get_mut(&current_file)
10866        {
10867            projections.retain(|projection| {
10868                projection.activation_byte != activation_byte
10869                    || !evicted.contains(&projection.required_guards)
10870            });
10871        }
10872        on_state();
10873
10874        // A fresh minimal set has no equal in the store: equality would have
10875        // been caught by the subset check above.
10876        projections_by_source
10877            .entry(current_file.clone())
10878            .or_default()
10879            .push(ConditionalIncludeProjection {
10880                activation_byte,
10881                required_guards: path.all.clone(),
10882                partial_guards: path.partial.clone(),
10883            });
10884
10885        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
10886            continue;
10887        };
10888        let mut nodes = vec![current_prepared.tree().root_node()];
10889        while let Some(node) = nodes.pop() {
10890            if node.kind() == "preproc_include" {
10891                let Some(include_guards) = include_directive_guard_requirements(
10892                    node,
10893                    current_prepared.source(),
10894                    reference_is_c,
10895                ) else {
10896                    continue;
10897                };
10898                let Some(reached) = path.merged(&include_guards) else {
10899                    continue;
10900                };
10901                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
10902                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10903                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
10904                        &current_file,
10905                        &include,
10906                        include_targets,
10907                    )) else {
10908                        continue;
10909                    };
10910                    pending.push((target, activation_byte, reached.clone()));
10911                }
10912                continue;
10913            }
10914            push_named_children_reversed(node, &mut nodes);
10915        }
10916    }
10917
10918    projections_by_source
10919        .into_iter()
10920        .map(|(source, mut projections)| {
10921            projections.sort_by_key(|projection| projection.activation_byte);
10922            (source, Arc::from(projections))
10923        })
10924        .collect()
10925}
10926
10927/// Decide one conditional include target without materializing every source
10928/// reached by every guard combination. A path `admission` already rejects
10929/// cannot become admissible after adding nested include guards -- both rules
10930/// only ever add requirements -- so discard it before expanding the next
10931/// header.
10932#[allow(clippy::too_many_arguments)]
10933fn find_conditional_include_projection_for_source(
10934    cpp: &dyn CppSource,
10935    token: QueryToken<'_>,
10936    file: &ProjectFile,
10937    prepared: &PreparedSyntaxTree,
10938    donor_source: &ProjectFile,
10939    admission: IncludePathAdmission,
10940    reference_guards: Option<&HashSet<PreprocessorGuard>>,
10941    reference_byte: usize,
10942    on_state: &dyn Fn(),
10943) -> bool {
10944    let Some(reference_guards) = reference_guards else {
10945        return false;
10946    };
10947    let reference_is_c = reference_uses_c_semantics(cpp, file);
10948    let include_targets = cpp.include_target_index();
10949    let mut pending = Vec::new();
10950    let mut nodes = vec![prepared.tree().root_node()];
10951    while let Some(node) = nodes.pop() {
10952        if node.kind() == "preproc_include" {
10953            let Some(required_guards) =
10954                include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10955            else {
10956                continue;
10957            };
10958            if node.end_byte() > reference_byte
10959                || !admission.admits(
10960                    &required_guards.all,
10961                    &required_guards.partial,
10962                    Some(reference_guards),
10963                )
10964            {
10965                continue;
10966            }
10967            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10968            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10969                let Some(target) = unique_include_target(resolve_include_targets_with_index(
10970                    file,
10971                    &include,
10972                    include_targets,
10973                )) else {
10974                    continue;
10975                };
10976                if &target == donor_source {
10977                    return true;
10978                }
10979                pending.push((target, required_guards.clone()));
10980            }
10981            continue;
10982        }
10983        push_named_children_reversed(node, &mut nodes);
10984    }
10985
10986    let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
10987    while let Some((current_file, path)) = pending.pop() {
10988        let guard_sets = expanded.entry(current_file.clone()).or_default();
10989        if guard_sets.contains(&path.all) {
10990            continue;
10991        }
10992        guard_sets.push(path.all.clone());
10993        on_state();
10994
10995        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
10996            continue;
10997        };
10998        let mut nodes = vec![current_prepared.tree().root_node()];
10999        while let Some(node) = nodes.pop() {
11000            if node.kind() == "preproc_include" {
11001                let Some(include_guards) = include_directive_guard_requirements(
11002                    node,
11003                    current_prepared.source(),
11004                    reference_is_c,
11005                ) else {
11006                    continue;
11007                };
11008                let Some(reached) = path.merged(&include_guards) else {
11009                    continue;
11010                };
11011                if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11012                    continue;
11013                }
11014                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
11015                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
11016                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
11017                        &current_file,
11018                        &include,
11019                        include_targets,
11020                    )) else {
11021                        continue;
11022                    };
11023                    if &target == donor_source {
11024                        return true;
11025                    }
11026                    pending.push((target, reached.clone()));
11027                }
11028                continue;
11029            }
11030            push_named_children_reversed(node, &mut nodes);
11031        }
11032    }
11033    false
11034}
11035
11036/// Whether `translation_unit`'s unconditional `#include` closure reaches
11037/// `header`, directly or through any chain of headers.
11038///
11039/// The include-closure question asked on its own, for
11040/// [`crate::identity::cpp_header_body_files_are_related`]. The walk resolves
11041/// each include the way visibility does -- to a unique target or to nothing --
11042/// so a duplicated basename relates nothing, and it is memoized per file pair
11043/// on the analyzer.
11044///
11045/// The reference position is `translation_unit` itself: the question is
11046/// whether that unit compiles the header, so that unit's own dialect and
11047/// preprocessor context govern the walk.
11048pub fn cpp_include_closure_reaches(
11049    cpp: &dyn CppSource,
11050    token: QueryToken<'_>,
11051    translation_unit: &ProjectFile,
11052    header: &ProjectFile,
11053) -> bool {
11054    unconditional_include_reaches(
11055        cpp,
11056        token,
11057        cpp.include_target_index(),
11058        translation_unit,
11059        header,
11060        translation_unit,
11061        &mut HashSet::default(),
11062    )
11063}
11064
11065fn unconditional_include_reaches(
11066    cpp: &dyn CppSource,
11067    token: QueryToken<'_>,
11068    include_targets: &IncludeTargetIndex,
11069    first: &ProjectFile,
11070    donor_source: &ProjectFile,
11071    reference_file: &ProjectFile,
11072    known_missing: &mut HashSet<ProjectFile>,
11073) -> bool {
11074    if first == donor_source {
11075        return true;
11076    }
11077    if known_missing.contains(first) {
11078        return false;
11079    }
11080    let reference_is_c = reference_file
11081        .rel_path()
11082        .extension()
11083        .and_then(|extension| extension.to_str())
11084        == Some("c");
11085    if let Some(reaches) =
11086        cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
11087    {
11088        return reaches;
11089    }
11090    let mut visited = HashSet::default();
11091    let mut files = vec![first.clone()];
11092    // Only an unconditional directive extends the include reach, so the walk
11093    // asks the question without a reference position.
11094    let reference = CallableReferenceContext {
11095        file: reference_file,
11096        position: None,
11097    };
11098    while let Some(file) = files.pop() {
11099        if file == *donor_source {
11100            cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
11101            return true;
11102        }
11103        if known_missing.contains(&file) || !visited.insert(file.clone()) {
11104            continue;
11105        }
11106        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11107            continue;
11108        };
11109        // One cursor for the whole-file walk; see `named_children_iter` (#3097).
11110        let mut cursor = prepared.tree().walk();
11111        let mut nodes = vec![prepared.tree().root_node()];
11112        while let Some(node) = nodes.pop() {
11113            if node.kind() == "preproc_include" {
11114                if callable_preprocessor_context_is_visible_for_reference(
11115                    node,
11116                    prepared.source(),
11117                    &reference,
11118                ) {
11119                    let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
11120                    for include in cpp_include_paths(std::slice::from_ref(&raw)) {
11121                        if let Some(target) = unique_include_target(
11122                            resolve_include_targets_with_index(&file, &include, include_targets),
11123                        ) {
11124                            files.push(target);
11125                        }
11126                    }
11127                }
11128                continue;
11129            }
11130            let first_pushed = nodes.len();
11131            nodes.extend(node.named_children(&mut cursor));
11132            nodes[first_pushed..].reverse();
11133        }
11134    }
11135    known_missing.extend(visited);
11136    cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
11137    false
11138}
11139
11140fn declaration_guard_requirements(
11141    analyzer: &CppGraphSource<'_>,
11142    cpp: &dyn CppSource,
11143    candidate: &CodeUnit,
11144) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11145    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11146        return Vec::new();
11147    };
11148    let root = prepared.tree().root_node();
11149    analyzer
11150        .ranges(candidate)
11151        .into_iter()
11152        .filter_map(|range| {
11153            root.descendant_for_byte_range(range.start_byte, range.end_byte)
11154                .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
11155                // A class name is injected into its own body at the declaration's
11156                // introduction point, not after the complete class range. Using
11157                // the start also preserves normal before/after ordering for aliases.
11158                .map(|required| (range.start_byte, required))
11159        })
11160        .collect()
11161}
11162
11163fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11164    analyzer
11165        .ranges(candidate)
11166        .into_iter()
11167        .map(|range| range.start_byte)
11168        .min()
11169}
11170
11171/// The macro names every configuration in `contexts` defines -- the fact set
11172/// one file's compile-database coverage proves (#2011). `None` when the
11173/// database has no entry for the file, which is different from an empty
11174/// intersection: no entry means no coverage, while an empty intersection is
11175/// covered-and-proves-nothing.
11176fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11177    let (first, rest) = contexts.split_first()?;
11178    Some(
11179        first
11180            .defined_macros
11181            .iter()
11182            .filter(|name| {
11183                rest.iter()
11184                    .all(|context| context.defined_macros.contains(*name))
11185            })
11186            .cloned()
11187            .collect(),
11188    )
11189}
11190
11191pub fn guard_requirements_hold_at_reference(
11192    required: &HashSet<PreprocessorGuard>,
11193    reference: Option<&HashSet<PreprocessorGuard>>,
11194) -> bool {
11195    reference.is_some_and(|active| {
11196        required
11197            .iter()
11198            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11199    })
11200}
11201
11202fn preprocessor_guard_holds_at_reference(
11203    required: &PreprocessorGuard,
11204    active: &HashSet<PreprocessorGuard>,
11205) -> bool {
11206    if active.contains(required) {
11207        return true;
11208    }
11209    let active_expression = BooleanGuardExpression::all(
11210        active
11211            .iter()
11212            .filter_map(PreprocessorGuard::as_boolean_expression),
11213    );
11214    required
11215        .as_boolean_expression()
11216        .is_some_and(|required| active_expression.implies(&required))
11217}
11218
11219/// Cross-file guard rule: two guard sets are compatible when neither one
11220/// contradicts the other. Use this instead of the subset test whenever the
11221/// guards come from a foreign file, which resolves its own conditionals
11222/// independently of the reference.
11223fn guards_compatible_at_reference(
11224    declaration: &HashSet<PreprocessorGuard>,
11225    reference: Option<&HashSet<PreprocessorGuard>>,
11226) -> bool {
11227    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11228}
11229
11230/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
11231/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
11232/// conditional.
11233///
11234/// Two declarations of one name that report the same chain stand in different
11235/// branches of it, so at most one of them is compiled in any configuration.
11236/// They are alternate spellings of a single declaration, not competing
11237/// declarations, and navigation must not present them as an ambiguity.
11238pub fn preprocessor_conditional_family_range(
11239    root: Node<'_>,
11240    start_byte: usize,
11241    end_byte: usize,
11242) -> Option<(usize, usize)> {
11243    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11244    let mut ancestor = Some(node);
11245    while let Some(current) = ancestor {
11246        if is_preprocessor_conditional(current)
11247            && preprocessor_conditional_contains_descendant(current, node)
11248        {
11249            let family = preprocessor_conditional_family_root(current);
11250            return Some((family.start_byte(), family.end_byte()));
11251        }
11252        ancestor = current.parent();
11253    }
11254    None
11255}
11256
11257fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
11258    let mut ancestor = node.parent();
11259    while let Some(current) = ancestor {
11260        if is_preprocessor_conditional(current)
11261            && preprocessor_conditional_contains_descendant(current, node)
11262        {
11263            let family = preprocessor_conditional_family_root(current);
11264            if preprocessor_conditional_family_has_terminal_else(family) {
11265                return Some(family);
11266            }
11267        }
11268        ancestor = current.parent();
11269    }
11270    None
11271}
11272
11273fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11274    while let Some(parent) = conditional.parent() {
11275        let is_alternative = parent
11276            .child_by_field_name("alternative")
11277            .is_some_and(|alternative| {
11278                alternative.start_byte() == conditional.start_byte()
11279                    && alternative.end_byte() == conditional.end_byte()
11280            });
11281        if !is_alternative {
11282            break;
11283        }
11284        conditional = parent;
11285    }
11286    conditional
11287}
11288
11289fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
11290    loop {
11291        let Some(alternative) = conditional.child_by_field_name("alternative") else {
11292            return false;
11293        };
11294        match alternative.kind() {
11295            "preproc_else" => return true,
11296            "preproc_elif" => conditional = alternative,
11297            _ => return false,
11298        }
11299    }
11300}
11301
11302/// The undecidable preprocessor guards an `#include` directive stands under,
11303/// or `None` when its conditional context keeps the directive out of a
11304/// translation unit of the reference's language: a false constant, a
11305/// condition this analyzer cannot read, or a `__cplusplus` branch that
11306/// language never takes.
11307///
11308/// This is [`callable_declaration_guard_requirements`]'s rule for the include
11309/// edge, and the language test is what keeps
11310/// [`IncludePathAdmission::Compatible`] honest: an empty active guard set
11311/// never implies `defined(__cplusplus)`, but it never contradicts it either,
11312/// so without this a `.c` file would reach the C++ arm of every
11313/// `#ifdef __cplusplus` / `#else` include pair.
11314fn include_directive_guard_requirements(
11315    node: Node<'_>,
11316    source: &str,
11317    reference_is_c: bool,
11318) -> Option<PreprocessorGuardEnvironment> {
11319    let required = preprocessor_guard_environment_by_family(node, source)?;
11320    let excluded_by_language = required.all.iter().any(|guard| match guard {
11321        PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11322        PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11323        _ => false,
11324    });
11325    (!excluded_by_language).then_some(required)
11326}
11327
11328pub fn preprocessor_guard_environment(
11329    node: Node<'_>,
11330    source: &str,
11331) -> Option<HashSet<PreprocessorGuard>> {
11332    preprocessor_guard_environment_by_family(node, source).map(|environment| environment.all)
11333}
11334
11335/// The preprocessor guards `node` stands under, split by whether the
11336/// conditional that contributes each one belongs to a family that covers every
11337/// configuration.
11338///
11339/// A complete `#if`/`#else` or `#if`/`#elif`/`#else` family is a case analysis
11340/// every configuration takes exactly one branch of, which is why the resolver
11341/// already reads a declaration through one (`exhaustive_guard_family_activation`,
11342/// `complementary_same_fqn_type_declarations`). A lone `#if` is not: nothing in
11343/// the source says any configuration defines that macro, so only the build can
11344/// decide it, and it stays in `partial`.
11345#[derive(Clone)]
11346struct PreprocessorGuardEnvironment {
11347    all: HashSet<PreprocessorGuard>,
11348    partial: HashSet<PreprocessorGuard>,
11349}
11350
11351impl PreprocessorGuardEnvironment {
11352    /// The environment of a path standing under both `self` and `other`, or
11353    /// `None` when the two contradict. A guard a lone `#if` contributes on
11354    /// either side stays undecided for the whole path.
11355    fn merged(&self, other: &Self) -> Option<Self> {
11356        Some(Self {
11357            all: merge_preprocessor_guards(&self.all, &other.all)?,
11358            partial: self.partial.union(&other.partial).cloned().collect(),
11359        })
11360    }
11361}
11362
11363fn preprocessor_guard_environment_by_family(
11364    node: Node<'_>,
11365    source: &str,
11366) -> Option<PreprocessorGuardEnvironment> {
11367    let mut all = HashSet::default();
11368    let mut partial = HashSet::default();
11369    let mut ancestor = node.parent();
11370    while let Some(conditional) = ancestor {
11371        if matches!(
11372            conditional.kind(),
11373            "preproc_if" | "preproc_ifdef" | "preproc_elif"
11374        ) && !is_file_covering_include_guard(conditional, source)
11375            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11376            && preprocessor_conditional_contains_descendant(conditional, node)
11377        {
11378            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11379            match guard {
11380                PreprocessorGuard::Constant(true) => {
11381                    ancestor = conditional.parent();
11382                    continue;
11383                }
11384                PreprocessorGuard::Constant(false) => return None,
11385                _ => {}
11386            }
11387            if all.contains(&guard.negated()) {
11388                return None;
11389            }
11390            if !preprocessor_conditional_family_has_terminal_else(
11391                preprocessor_conditional_family_root(conditional),
11392            ) {
11393                partial.insert(guard.clone());
11394            }
11395            all.insert(guard);
11396        }
11397        ancestor = conditional.parent();
11398    }
11399    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11400        match guard {
11401            PreprocessorGuard::Constant(true) => {}
11402            PreprocessorGuard::Constant(false) => return None,
11403            _ => {
11404                if all.contains(&guard.negated()) {
11405                    return None;
11406                }
11407                // The family of a conditional the parser split across a
11408                // statement boundary is not readable from the tree, so it
11409                // counts as undecided.
11410                partial.insert(guard.clone());
11411                all.insert(guard);
11412            }
11413        }
11414    }
11415    Some(PreprocessorGuardEnvironment { all, partial })
11416}
11417
11418fn fragmented_statement_preprocessor_guard(
11419    descendant: Node<'_>,
11420    source: &str,
11421) -> Option<PreprocessorGuard> {
11422    // A conditional that starts before `} else if (...) {` crosses the
11423    // enclosing statement's grammar boundary. tree-sitter leaves its opener
11424    // as a `preproc_if` with a missing terminator in the consequence and
11425    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
11426    // those structured nodes before restoring the guard to intervening uses.
11427    let mut ancestor = descendant.parent();
11428    while let Some(statement) = ancestor {
11429        if statement.kind() == "if_statement"
11430            && let (Some(consequence), Some(alternative)) = (
11431                statement.child_by_field_name("consequence"),
11432                statement.child_by_field_name("alternative"),
11433            )
11434            && alternative.start_byte() <= descendant.start_byte()
11435            && descendant.end_byte() <= alternative.end_byte()
11436        {
11437            let mut cursor = consequence.walk();
11438            let openers = consequence
11439                .named_children(&mut cursor)
11440                .filter(|child| {
11441                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11442                        && child
11443                            .child(child.child_count().saturating_sub(1))
11444                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11445                })
11446                .collect::<Vec<_>>();
11447            if openers.len() != 1 {
11448                ancestor = statement.parent();
11449                continue;
11450            }
11451
11452            let mut terminators = Vec::new();
11453            let mut stack = vec![alternative];
11454            while let Some(node) = stack.pop() {
11455                if node.kind() == "preproc_call"
11456                    && node.start_byte() >= descendant.end_byte()
11457                    && node
11458                        .child_by_field_name("directive")
11459                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11460                {
11461                    terminators.push(node);
11462                    continue;
11463                }
11464                push_named_children_reversed(node, &mut stack);
11465            }
11466            if terminators.len() == 1 {
11467                return simple_preprocessor_guard(openers[0], source);
11468            }
11469        }
11470        ancestor = statement.parent();
11471    }
11472    None
11473}
11474
11475fn preprocessor_guard_for_descendant(
11476    conditional: Node<'_>,
11477    descendant: Node<'_>,
11478    source: &str,
11479) -> Option<PreprocessorGuard> {
11480    let mut guard = simple_preprocessor_guard(conditional, source)?;
11481    if conditional
11482        .child_by_field_name("alternative")
11483        .is_some_and(|alternative| {
11484            alternative.start_byte() <= descendant.start_byte()
11485                && descendant.end_byte() <= alternative.end_byte()
11486        })
11487    {
11488        let alternative = conditional.child_by_field_name("alternative")?;
11489        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
11490        // descendant in any later branch must first exclude the parent branch,
11491        // then collect the nested `preproc_elif` guard from its own ancestor.
11492        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
11493            return None;
11494        }
11495        guard = guard.negated();
11496    }
11497    Some(guard)
11498}
11499
11500fn preprocessor_conditional_contains_descendant(
11501    conditional: Node<'_>,
11502    descendant: Node<'_>,
11503) -> bool {
11504    cpp_displaced_preprocessor_boundary(conditional)
11505        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11506}
11507
11508pub fn merge_preprocessor_guards(
11509    left: &HashSet<PreprocessorGuard>,
11510    right: &HashSet<PreprocessorGuard>,
11511) -> Option<HashSet<PreprocessorGuard>> {
11512    let mut merged = left.clone();
11513    for guard in right {
11514        let boolean_negation = guard
11515            .as_boolean_expression()
11516            .map(|expression| expression.negated());
11517        if merged.contains(&guard.negated())
11518            || boolean_negation.is_some_and(|negated| {
11519                merged
11520                    .iter()
11521                    .filter_map(PreprocessorGuard::as_boolean_expression)
11522                    .any(|existing| existing == negated)
11523            })
11524        {
11525            return None;
11526        }
11527        merged.insert(guard.clone());
11528    }
11529    Some(merged)
11530}
11531
11532fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11533    if conditional.kind() == "preproc_ifdef" {
11534        let name = conditional.child_by_field_name("name")?;
11535        let name = node_text(name, source).to_string();
11536        return match conditional.child(0)?.kind() {
11537            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11538            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11539            _ => None,
11540        };
11541    }
11542    let condition = conditional.child_by_field_name("condition")?;
11543    simple_preprocessor_expression_guard(condition, source).or_else(|| {
11544        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
11545            node_text(condition, source),
11546        )))
11547    })
11548}
11549
11550fn simple_preprocessor_expression_guard(
11551    expression: Node<'_>,
11552    source: &str,
11553) -> Option<PreprocessorGuard> {
11554    match expression.kind() {
11555        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
11556            node_text(expression, source).to_string(),
11557        ))),
11558        "number_literal" => match node_text(expression, source).trim() {
11559            "0" => Some(PreprocessorGuard::Constant(false)),
11560            "1" => Some(PreprocessorGuard::Constant(true)),
11561            _ => None,
11562        },
11563        "preproc_defined" => {
11564            let identifier = (0..expression.named_child_count())
11565                .filter_map(|index| expression.named_child(index))
11566                .find(|child| child.kind() == "identifier")?;
11567            Some(PreprocessorGuard::Defined(
11568                node_text(identifier, source).to_string(),
11569            ))
11570        }
11571        "unary_expression"
11572            if expression
11573                .child_by_field_name("operator")
11574                .is_some_and(|operator| operator.kind() == "!") =>
11575        {
11576            simple_preprocessor_expression_guard(
11577                expression.child_by_field_name("argument")?,
11578                source,
11579            )
11580            .map(|guard| guard.negated())
11581        }
11582        "parenthesized_expression" => (0..expression.named_child_count())
11583            .filter_map(|index| expression.named_child(index))
11584            .next()
11585            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
11586        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
11587            expression, source,
11588        ))),
11589        _ => None,
11590    }
11591}
11592
11593fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
11594    match expression.kind() {
11595        "number_literal" => match node_text(expression, source).trim() {
11596            "0" => BooleanGuardExpression::Constant(false),
11597            "1" => BooleanGuardExpression::Constant(true),
11598            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11599                expression, source,
11600            ))),
11601        },
11602        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
11603        "preproc_defined" => {
11604            let identifier = (0..expression.named_child_count())
11605                .filter_map(|index| expression.named_child(index))
11606                .find(|child| child.kind() == "identifier");
11607            identifier.map_or_else(
11608                || {
11609                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11610                        expression, source,
11611                    )))
11612                },
11613                |identifier| {
11614                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
11615                },
11616            )
11617        }
11618        "unary_expression"
11619            if expression
11620                .child_by_field_name("operator")
11621                .is_some_and(|operator| operator.kind() == "!") =>
11622        {
11623            expression.child_by_field_name("argument").map_or_else(
11624                || {
11625                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11626                        expression, source,
11627                    )))
11628                },
11629                |argument| boolean_preprocessor_expression(argument, source).negated(),
11630            )
11631        }
11632        "parenthesized_expression" => (0..expression.named_child_count())
11633            .filter_map(|index| expression.named_child(index))
11634            .next()
11635            .map_or_else(
11636                || {
11637                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11638                        expression, source,
11639                    )))
11640                },
11641                |child| boolean_preprocessor_expression(child, source),
11642            ),
11643        "binary_expression" => {
11644            let operands = || {
11645                Some((
11646                    boolean_preprocessor_expression(
11647                        expression.child_by_field_name("left")?,
11648                        source,
11649                    ),
11650                    boolean_preprocessor_expression(
11651                        expression.child_by_field_name("right")?,
11652                        source,
11653                    ),
11654                ))
11655            };
11656            match expression
11657                .child_by_field_name("operator")
11658                .map(|operator| operator.kind())
11659            {
11660                Some("&&") => operands().map_or_else(
11661                    || {
11662                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11663                            expression, source,
11664                        )))
11665                    },
11666                    |(left, right)| BooleanGuardExpression::all([left, right]),
11667                ),
11668                Some("||") => operands().map_or_else(
11669                    || {
11670                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11671                            expression, source,
11672                        )))
11673                    },
11674                    |(left, right)| BooleanGuardExpression::any([left, right]),
11675                ),
11676                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11677                    expression, source,
11678                ))),
11679            }
11680        }
11681        _ => {
11682            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
11683        }
11684    }
11685}
11686
11687fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
11688    if targets.len() == 1 {
11689        targets.pop()
11690    } else {
11691        None
11692    }
11693}
11694
11695/// The declaration nodes of `candidate` in `prepared` that stand at a scope a
11696/// later reference can name.
11697///
11698/// A declaration inside a real function body, lambda, or nested block is block
11699/// local and is dropped. A declaration inside a parser-recovery wrapper that
11700/// merely looks callable -- an export macro between `class` and its name, or a
11701/// namespace-opening macro token before `namespace x {` -- keeps class or
11702/// namespace scope and is kept.
11703fn nameable_callable_declaration_nodes<'tree>(
11704    analyzer: &CppGraphSource<'_>,
11705    prepared: &'tree PreparedSyntaxTree,
11706    candidate: &CodeUnit,
11707) -> Vec<Node<'tree>> {
11708    callable_declaration_nodes(analyzer, prepared, candidate)
11709        .into_iter()
11710        .filter(|declaration| {
11711            let mut ancestor = declaration.parent();
11712            while let Some(node) = ancestor {
11713                if node.kind() == "function_definition"
11714                    && is_recovered_declaration_scope_container(node, prepared.source())
11715                {
11716                    ancestor = node.parent();
11717                    continue;
11718                }
11719                if node.kind() == "compound_statement"
11720                    && node.parent().is_some_and(|parent| {
11721                        is_recovered_declaration_scope_container(parent, prepared.source())
11722                    })
11723                {
11724                    ancestor = node.parent().and_then(|parent| parent.parent());
11725                    continue;
11726                }
11727                if matches!(
11728                    node.kind(),
11729                    "compound_statement" | "function_definition" | "lambda_expression"
11730                ) {
11731                    return false;
11732                }
11733                ancestor = node.parent();
11734            }
11735            true
11736        })
11737        .collect()
11738}
11739
11740fn callable_declaration_nodes<'tree>(
11741    analyzer: &CppGraphSource<'_>,
11742    prepared: &'tree PreparedSyntaxTree,
11743    candidate: &CodeUnit,
11744) -> Vec<Node<'tree>> {
11745    let root = prepared.tree().root_node();
11746    analyzer
11747        .ranges(candidate)
11748        .into_iter()
11749        .filter_map(|range| {
11750            let mut declaration =
11751                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
11752            // A declaration an attribute-like macro invocation swallowed lives
11753            // inside the `ERROR` the parser left, not inside a `declaration`
11754            // node, so that envelope is where the climb stops (#2552).
11755            //
11756            // The climb can also run out of ancestors. A collapse that takes a
11757            // whole file with it -- whisper.cpp's `include/whisper.h` from its
11758            // first `WHISPER_DEPRECATED(` to the end (#3094) -- leaves no
11759            // envelope around most of what a region reparse recovers there, and
11760            // the recovered declaration's own `;` can fall outside the envelope
11761            // it does leave, so the range spans the container rather than one
11762            // node. Either way the declaration stands at the scope the climb
11763            // ended in, which is what the callers ask these nodes about.
11764            while !matches!(
11765                declaration.kind(),
11766                "declaration" | "field_declaration" | "function_definition"
11767            ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
11768                declaration,
11769                prepared.source(),
11770            ) {
11771                let Some(parent) = declaration.parent() else {
11772                    break;
11773                };
11774                declaration = parent;
11775            }
11776            Some(declaration)
11777        })
11778        .collect()
11779}
11780
11781fn real_function_definition_ancestor<'tree>(
11782    node: Node<'tree>,
11783    source: &str,
11784) -> Option<Node<'tree>> {
11785    let mut ancestor = node.parent();
11786    while let Some(node) = ancestor {
11787        if node.kind() == "function_definition"
11788            && !is_recovered_declaration_scope_container(node, source)
11789        {
11790            return Some(node);
11791        }
11792        ancestor = node.parent();
11793    }
11794    None
11795}
11796
11797fn callable_declaration_activation_in_file(
11798    analyzer: &CppGraphSource<'_>,
11799    prepared: &PreparedSyntaxTree,
11800    candidate: &CodeUnit,
11801    reference: &CallableReferenceContext<'_>,
11802) -> Option<usize> {
11803    nameable_callable_declaration_nodes(analyzer, prepared, candidate)
11804        .into_iter()
11805        .filter(|declaration| {
11806            callable_preprocessor_context_is_visible_for_reference(
11807                *declaration,
11808                prepared.source(),
11809                reference,
11810            )
11811        })
11812        .map(callable_declaration_activation_byte)
11813        .min()
11814}
11815
11816/// C and C++ activate a declared name at the end of its declarator, not at the
11817/// end of the whole declaration. A function definition ends at the closing
11818/// brace of its body, so the declaration end byte would hide the function from
11819/// its own body and make self recursion unresolvable without a prototype.
11820fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
11821    if declaration.kind() != "function_definition" {
11822        return declaration.end_byte();
11823    }
11824    declaration
11825        .child_by_field_name("declarator")
11826        .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
11827}
11828
11829/// The reference side of a callable visibility question.
11830///
11831/// An include-graph walk and a whole-file arity activation ask the question
11832/// without one reference position, so they carry no `position` and therefore no
11833/// guard environment.
11834struct CallableReferenceContext<'a> {
11835    file: &'a ProjectFile,
11836    position: Option<CallableReferencePosition<'a>>,
11837}
11838
11839/// One reference position plus its preprocessor guard environment. The
11840/// environment is computed on demand because most declarations carry no
11841/// non-trivial guard.
11842struct CallableReferencePosition<'a> {
11843    prepared: &'a PreparedSyntaxTree,
11844    byte: usize,
11845    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
11846}
11847
11848impl CallableReferenceContext<'_> {
11849    fn is_c(&self) -> bool {
11850        self.file
11851            .rel_path()
11852            .extension()
11853            .and_then(|extension| extension.to_str())
11854            == Some("c")
11855    }
11856
11857    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
11858        let position = self.position.as_ref()?;
11859        position
11860            .guards
11861            .get_or_init(|| {
11862                position
11863                    .prepared
11864                    .tree()
11865                    .root_node()
11866                    .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
11867                    .and_then(|node| {
11868                        preprocessor_guard_environment(node, position.prepared.source())
11869                    })
11870            })
11871            .as_ref()
11872    }
11873}
11874
11875/// The undecidable preprocessor guards `node` stands under, or `None` when its
11876/// conditional context excludes it from `reference`'s translation unit outright:
11877/// a false constant, a condition this analyzer cannot read, or a `__cplusplus`
11878/// branch the reference's language never takes.
11879///
11880/// Collecting one guard per enclosing conditional makes the whole walk a
11881/// conjunction of the declaration's requirements. Which comparison those
11882/// requirements then face depends on where the declaration lives: a same-file
11883/// declaration shares the reference's configuration, so the reference's active
11884/// guards must imply them, while a foreign file resolves its own conditionals
11885/// and only has to stay compatible with the reference.
11886fn callable_declaration_guard_requirements(
11887    node: Node<'_>,
11888    source: &str,
11889    reference: &CallableReferenceContext<'_>,
11890) -> Option<HashSet<PreprocessorGuard>> {
11891    let reference_is_c = reference.is_c();
11892    let mut required = HashSet::default();
11893    let mut ancestor = node.parent();
11894    while let Some(conditional) = ancestor {
11895        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11896            && !is_file_covering_include_guard(conditional, source)
11897            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11898            && preprocessor_conditional_contains_descendant(conditional, node)
11899        {
11900            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11901            match guard {
11902                PreprocessorGuard::Constant(true) => {}
11903                PreprocessorGuard::Constant(false) => return None,
11904                PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
11905                    if reference_is_c {
11906                        return None;
11907                    }
11908                }
11909                PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
11910                    if !reference_is_c {
11911                        return None;
11912                    }
11913                }
11914                guard => {
11915                    required.insert(guard);
11916                }
11917            }
11918        }
11919        ancestor = conditional.parent();
11920    }
11921    Some(required)
11922}
11923
11924/// Whether a declaration in the reference's own file is co-active with the
11925/// reference: one translation unit resolves every conditional the same way, so
11926/// the reference's active guards must imply the declaration's requirements.
11927fn callable_preprocessor_context_is_visible_for_reference(
11928    node: Node<'_>,
11929    source: &str,
11930    reference: &CallableReferenceContext<'_>,
11931) -> bool {
11932    let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
11933        return false;
11934    };
11935    required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards())
11936}
11937
11938fn flattened_macro_namespace_declaration_matches(
11939    analyzer: &CppGraphSource<'_>,
11940    cpp: &dyn CppSource,
11941    reference_file: &ProjectFile,
11942    visible_declaration: &CodeUnit,
11943    qualified_candidate: &CodeUnit,
11944    reference_byte: usize,
11945) -> bool {
11946    // Namespace-opening macros can leave tree-sitter unable to retain the
11947    // namespace owner after a later recovery point. In that shape the forward
11948    // declaration is indexed at translation-unit scope, while the definition
11949    // still has its qualified owner. Require all surviving structural evidence
11950    // before treating the declaration as activation for that definition.
11951    if visible_declaration.kind() != qualified_candidate.kind()
11952        || visible_declaration.identifier() != qualified_candidate.identifier()
11953        || visible_declaration.signature() != qualified_candidate.signature()
11954        || !visible_declaration.package_name().is_empty()
11955        || qualified_candidate.package_name().is_empty()
11956    {
11957        return false;
11958    }
11959
11960    let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
11961        return false;
11962    };
11963    let root = prepared.tree().root_node();
11964    let closing_brace_limit = if visible_declaration.source() == reference_file {
11965        reference_byte
11966    } else {
11967        usize::MAX
11968    };
11969
11970    analyzer
11971        .ranges(visible_declaration)
11972        .into_iter()
11973        .any(|range| {
11974            let Some(mut declaration) =
11975                root.descendant_for_byte_range(range.start_byte, range.end_byte)
11976            else {
11977                return false;
11978            };
11979            while !matches!(
11980                declaration.kind(),
11981                "declaration" | "field_declaration" | "function_definition"
11982            ) {
11983                let Some(parent) = declaration.parent() else {
11984                    return false;
11985                };
11986                declaration = parent;
11987            }
11988            if declaration
11989                .parent()
11990                .is_none_or(|parent| parent.kind() != "translation_unit")
11991                || !macro_displaced_cpp_return_type(declaration, prepared.source())
11992            {
11993                return false;
11994            }
11995
11996            let mut cursor = root.walk();
11997            root.named_children(&mut cursor).any(|sibling| {
11998                sibling.start_byte() >= declaration.end_byte()
11999                    && sibling.start_byte() < closing_brace_limit
12000                    && direct_unmatched_closing_brace(sibling)
12001            })
12002        })
12003}
12004
12005fn flattened_macro_namespace_components(
12006    declaration: Node<'_>,
12007    source: &str,
12008) -> Option<Vec<String>> {
12009    flattened_macro_function_namespace_components(declaration, source)
12010        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12011}
12012
12013fn flattened_macro_function_namespace_components(
12014    declaration: Node<'_>,
12015    source: &str,
12016) -> Option<Vec<String>> {
12017    let body = declaration
12018        .parent()
12019        .filter(|parent| parent.kind() == "compound_statement")?;
12020    let function = body.parent()?;
12021    if function.child_by_field_name("body") != Some(body) {
12022        return None;
12023    }
12024    let namespace_name = recovered_macro_namespace_name(function, source)?;
12025    let mut components = enclosing_namespace_components(declaration, source)?;
12026    components.push(namespace_name);
12027    Some(components)
12028}
12029
12030/// The namespace name a namespace-opening macro token displaced into a
12031/// synthetic `function_definition`, or `None` when `function` is not that
12032/// recovery shape.
12033///
12034/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
12035/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
12036/// the macro token, whose declarator is the namespace name behind an `ERROR`
12037/// holding the `namespace` keyword, and whose body spans the whole namespace
12038/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
12039/// artifact from a real function definition.
12040fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
12041    if function.kind() != "function_definition" || !function.has_error() {
12042        return None;
12043    }
12044    let body = function
12045        .child_by_field_name("body")
12046        .filter(|body| body.kind() == "compound_statement")?;
12047    let mut cursor = function.walk();
12048    let prefix = function
12049        .named_children(&mut cursor)
12050        .take_while(|child| child.start_byte() < body.start_byte())
12051        .filter(|child| child.kind() != "comment")
12052        .collect::<Vec<_>>();
12053    let begin_index = prefix.iter().rposition(|child| {
12054        flattened_macro_sentinel_name(*child, source)
12055            .is_some_and(|name| is_namespace_begin_sentinel(&name))
12056    })?;
12057    let mut identifiers = Vec::new();
12058    let mut stack = prefix[begin_index + 1..]
12059        .iter()
12060        .rev()
12061        .copied()
12062        .collect::<Vec<_>>();
12063    while let Some(current) = stack.pop() {
12064        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
12065            identifiers.push(identifier);
12066            continue;
12067        }
12068        let mut cursor = current.walk();
12069        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
12070        stack.extend(children.into_iter().rev());
12071    }
12072    let [keyword, namespace_name] = identifiers.as_slice() else {
12073        return None;
12074    };
12075    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
12076    {
12077        return None;
12078    }
12079    let mut next = function.next_named_sibling();
12080    let next = loop {
12081        let candidate = next?;
12082        next = candidate.next_named_sibling();
12083        if candidate.kind() != "comment" {
12084            break candidate;
12085        }
12086    };
12087    flattened_macro_sentinel_name(next, source)
12088        .is_some_and(|name| is_namespace_end_sentinel(&name))
12089        .then(|| namespace_name.clone())
12090}
12091
12092/// A `function_definition` that exists only because tree-sitter recovered a
12093/// macro-decorated class head or a namespace-opening macro token. A declaration
12094/// in such a body keeps class or namespace scope, so a scope walk must step over
12095/// the wrapper instead of treating the declaration as block local.
12096fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
12097    crate::declarations::is_recovered_exported_class_container(node, source)
12098        || crate::declarations::is_recovered_fragmented_partial_specialization_container(
12099            node, source,
12100        )
12101        || recovered_macro_namespace_name(node, source).is_some()
12102}
12103
12104fn flattened_macro_error_namespace_components(
12105    declaration: Node<'_>,
12106    source: &str,
12107) -> Option<Vec<String>> {
12108    let parent = declaration
12109        .parent()
12110        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
12111    let mut cursor = parent.walk();
12112    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
12113    let declaration_index = siblings
12114        .iter()
12115        .position(|candidate| same_node(*candidate, declaration))?;
12116    let begin_index = (0..declaration_index).rev().find(|index| {
12117        flattened_macro_sentinel_name(siblings[*index], source)
12118            .is_some_and(|name| is_namespace_begin_sentinel(&name))
12119    })?;
12120
12121    let significant = siblings[begin_index + 1..declaration_index]
12122        .iter()
12123        .copied()
12124        .filter(|node| node.kind() != "comment")
12125        .collect::<Vec<_>>();
12126    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
12127        return None;
12128    };
12129    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
12130        return None;
12131    }
12132    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
12133    if significant[2..].iter().any(|node| {
12134        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
12135            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
12136        })
12137    }) {
12138        return None;
12139    }
12140
12141    let mut saw_namespace_close = false;
12142    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
12143        if sibling.kind() == "comment" {
12144            continue;
12145        }
12146        if !saw_namespace_close {
12147            if direct_unmatched_closing_brace(sibling) {
12148                saw_namespace_close = true;
12149                continue;
12150            }
12151            if flattened_macro_sentinel_name(sibling, source).is_some() {
12152                return None;
12153            }
12154            continue;
12155        }
12156        if !flattened_macro_sentinel_name(sibling, source)
12157            .is_some_and(|name| is_namespace_end_sentinel(&name))
12158        {
12159            return None;
12160        }
12161        let mut components = enclosing_namespace_components(declaration, source)?;
12162        components.push(namespace_name);
12163        return Some(components);
12164    }
12165    None
12166}
12167
12168fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12169    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
12170    // an `expression_statement` with a missing semicolon; inside a namespace
12171    // body the same token stays a bare `type_identifier`.
12172    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12173        node.named_child(0)?
12174    } else {
12175        node
12176    };
12177    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
12178        node.child_by_field_name("type")
12179            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
12180    })?;
12181    (cpp_export_macro_token(&candidate)
12182        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
12183    .then_some(candidate)
12184}
12185
12186/// Namespace-opening macros are spelled both ways in the wild:
12187/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
12188fn is_namespace_begin_sentinel(name: &str) -> bool {
12189    name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
12190}
12191
12192fn is_namespace_end_sentinel(name: &str) -> bool {
12193    name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
12194}
12195
12196fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12197    if node.kind() != "ERROR" || node.named_child_count() != 1 {
12198        return None;
12199    }
12200    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12201    (!cpp_export_macro_token(&name)).then_some(name)
12202}
12203
12204fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12205    if !matches!(
12206        node.kind(),
12207        "identifier" | "namespace_identifier" | "type_identifier"
12208    ) {
12209        return None;
12210    }
12211    let name = normalize_cpp_whitespace(node_text(node, source));
12212    (!name.is_empty()).then_some(name)
12213}
12214
12215fn guard_requirement_sets_match(
12216    left: &[(usize, HashSet<PreprocessorGuard>)],
12217    right: &[(usize, HashSet<PreprocessorGuard>)],
12218) -> bool {
12219    left.len() == right.len()
12220        && left.iter().all(|(_, left_guards)| {
12221            right
12222                .iter()
12223                .any(|(_, right_guards)| left_guards == right_guards)
12224        })
12225        && right.iter().all(|(_, right_guards)| {
12226            left.iter()
12227                .any(|(_, left_guards)| right_guards == left_guards)
12228        })
12229}
12230
12231fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
12232    let Some(type_node) = declaration.child_by_field_name("type") else {
12233        return false;
12234    };
12235    let type_name = normalize_cpp_whitespace(node_text(type_node, source));
12236    !type_name.is_empty()
12237        && type_name
12238            .chars()
12239            .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
12240        && (0..declaration.named_child_count()).any(|index| {
12241            declaration
12242                .named_child(index)
12243                .is_some_and(|child| child.kind() == "ERROR")
12244        })
12245}
12246
12247fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12248    node.kind() == "ERROR"
12249        && (0..node.child_count())
12250            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12251}
12252
12253pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12254    let mut ancestor = node.parent();
12255    while let Some(parent) = ancestor {
12256        if is_preprocessor_conditional(parent)
12257            && !is_file_covering_include_guard(parent, source)
12258            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12259        {
12260            return false;
12261        }
12262        ancestor = parent.parent();
12263    }
12264    true
12265}
12266
12267fn is_split_cpp_language_linkage_wrapper(
12268    conditional: Node<'_>,
12269    descendant: Node<'_>,
12270    source: &str,
12271) -> bool {
12272    if conditional.child_by_field_name("alternative").is_some()
12273        || !matches!(
12274            simple_preprocessor_guard(conditional, source),
12275            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12276        )
12277    {
12278        return false;
12279    }
12280    let mut current = descendant.parent();
12281    let linkage = loop {
12282        let Some(node) = current else {
12283            return false;
12284        };
12285        if node == conditional {
12286            return false;
12287        }
12288        if node.kind() == "linkage_specification" {
12289            break node;
12290        }
12291        current = node.parent();
12292    };
12293    if linkage
12294        .child_by_field_name("value")
12295        .is_none_or(|value| node_text(value, source) != "\"C\"")
12296    {
12297        return false;
12298    }
12299    let Some(body) = linkage.child_by_field_name("body") else {
12300        return false;
12301    };
12302    let closes_opening_branch = (0..body.named_child_count())
12303        .filter_map(|index| body.named_child(index))
12304        .take_while(|child| child.end_byte() <= descendant.start_byte())
12305        .any(|child| {
12306            child.kind() == "preproc_call"
12307                && child
12308                    .child_by_field_name("directive")
12309                    .is_some_and(|directive| node_text(directive, source) == "#endif")
12310        });
12311    let reopens_for_closing_brace = (0..body.named_child_count())
12312        .filter_map(|index| body.named_child(index))
12313        .skip_while(|child| child.start_byte() < descendant.end_byte())
12314        .any(|child| {
12315            matches!(
12316                simple_preprocessor_guard(child, source),
12317                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12318            ) && (0..child.child_count()).any(|index| {
12319                child
12320                    .child(index)
12321                    .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
12322            })
12323        });
12324    closes_opening_branch && reopens_for_closing_brace
12325}
12326
12327/// The argument list a call-shaped node supplies: `f(args)`, `new T(args)`,
12328/// `T{args}` and the member initializer `: field(args)`, whose grammar gives its
12329/// argument list no field name.
12330pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12331    node.child_by_field_name("arguments")
12332        .or_else(|| node.child_by_field_name("parameters"))
12333        .or_else(|| node.child_by_field_name("value"))
12334        .or_else(|| first_named_child_of_kind(node, "argument_list"))
12335        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12336}
12337
12338pub fn call_arity(node: Node<'_>) -> usize {
12339    call_arguments_node(node)
12340        .map(|args| argument_children(args).count())
12341        .unwrap_or(0)
12342}
12343
12344pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12345    let recovered_block_arguments = recovered_block_literal_arguments(node);
12346    (0..node.child_count())
12347        .filter_map(move |index| node.child(index))
12348        .filter(|child| child.is_named() && !child.is_extra())
12349        .flat_map(move |child| {
12350            if let Some((raw, left, right)) = recovered_block_arguments
12351                && child == raw
12352            {
12353                [Some(left), Some(right)]
12354            } else {
12355                [Some(child), None]
12356            }
12357        })
12358        .flatten()
12359}
12360
12361/// Recover the two ordinary C values that the C++ grammar folds into one
12362/// `new_expression` for `callee(new, trailing)`.
12363///
12364/// The malformed node has an exact grammar-owned shape: the anonymous `new`
12365/// token, an extra `ERROR` containing only the comma token, and the trailing
12366/// value in the `type` field.  The caller supplies the compilation-dialect
12367/// proof from [`reference_uses_c_semantics`]; a C++ source therefore never
12368/// reinterprets a real new-expression through this path.
12369pub fn recovered_c_new_expression_arguments(
12370    node: Node<'_>,
12371    uses_c_semantics: bool,
12372) -> Option<[Node<'_>; 2]> {
12373    if !uses_c_semantics || node.kind() != "new_expression" {
12374        return None;
12375    }
12376    let parent = node.parent()?;
12377    if parent.kind() != "argument_list" {
12378        return None;
12379    }
12380    let keyword = node.child(0)?;
12381    let error = node.child(1)?;
12382    let trailing = node.child(2)?;
12383    if node.child(3).is_some()
12384        || keyword.kind() != "new"
12385        || keyword.is_named()
12386        || keyword.child_count() != 0
12387        || error.kind() != "ERROR"
12388        || !error.is_extra()
12389        || error.child_count() != 1
12390        || error.child(0).is_none_or(|comma| comma.kind() != ",")
12391        || node.child_by_field_name("type") != Some(trailing)
12392        || trailing.kind() != "type_identifier"
12393    {
12394        return None;
12395    }
12396    Some([keyword, trailing])
12397}
12398
12399/// The recovered C value covering one focused source range, starting from any
12400/// node within the malformed new-expression.
12401pub fn recovered_c_new_expression_argument_at(
12402    mut node: Node<'_>,
12403    start_byte: usize,
12404    end_byte: usize,
12405    uses_c_semantics: bool,
12406) -> Option<Node<'_>> {
12407    // The recovery only exists for C sources, and the climb below reaches the
12408    // root before it can answer no. `Node::parent` re-descends from the root on
12409    // every step, so leaving the check inside the loop made every scanned node
12410    // of a C++ file pay a full quadratic ancestor walk for an answer settled by
12411    // the file's language (#3097).
12412    if !uses_c_semantics {
12413        return None;
12414    }
12415    loop {
12416        if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12417            return arguments.into_iter().find(|argument| {
12418                argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12419            });
12420        }
12421        node = node.parent()?;
12422    }
12423}
12424
12425fn recovered_c_keyword_argument_count(
12426    file: &ProjectFile,
12427    call: Node<'_>,
12428    arguments: Node<'_>,
12429    source: &str,
12430) -> usize {
12431    // A C identifier that is a C++ keyword can be displaced twice by the C++
12432    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
12433    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
12434    // the enclosing C function before restoring the otherwise dropped slot.
12435    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12436        return 0;
12437    }
12438    let mut ancestor = Some(call);
12439    let function = loop {
12440        let Some(current) = ancestor else {
12441            return 0;
12442        };
12443        if current.kind() == "function_definition" {
12444            break current;
12445        }
12446        ancestor = current.parent();
12447    };
12448    let Some(parameters) = function
12449        .child_by_field_name("declarator")
12450        .and_then(|declarator| declarator.child_by_field_name("parameters"))
12451    else {
12452        return 0;
12453    };
12454    let displaced_parameter_keywords = (0..parameters.child_count())
12455        .filter_map(|index| parameters.child(index))
12456        .filter(|error| error.kind() == "ERROR")
12457        .filter_map(|error| {
12458            let parameter = error.prev_named_sibling()?;
12459            if parameter.kind() != "parameter_declaration"
12460                || parameter.end_byte() != error.start_byte()
12461                || extract_variable_name(parameter, source).is_some()
12462            {
12463                return None;
12464            }
12465            let mut children = (0..error.child_count())
12466                .filter_map(|index| error.child(index))
12467                .filter(|child| !child.is_extra() && !child.is_missing());
12468            let keyword = children.next()?;
12469            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12470                .then_some(keyword)
12471        })
12472        .collect::<Vec<_>>();
12473    if displaced_parameter_keywords.is_empty() {
12474        return 0;
12475    }
12476
12477    (0..arguments.child_count())
12478        .filter_map(|index| arguments.child(index))
12479        .filter(|error| error.kind() == "ERROR" && error.is_extra())
12480        .filter(|error| {
12481            let mut children = (0..error.child_count())
12482                .filter_map(|index| error.child(index))
12483                .filter(|child| !child.is_extra() && !child.is_missing());
12484            let Some(comma) = children.next() else {
12485                return false;
12486            };
12487            let Some(keyword) = children.next() else {
12488                return false;
12489            };
12490            children.next().is_none()
12491                && comma.kind() == ","
12492                && !keyword.is_named()
12493                && keyword.child_count() == 0
12494                && displaced_parameter_keywords
12495                    .iter()
12496                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
12497        })
12498        .count()
12499}
12500
12501fn recovered_block_literal_arguments<'tree>(
12502    arguments: Node<'tree>,
12503) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
12504    if arguments.kind() != "argument_list" {
12505        return None;
12506    }
12507    let mut raw_arguments = (0..arguments.child_count())
12508        .filter_map(|index| arguments.child(index))
12509        .filter(|child| child.is_named() && !child.is_extra());
12510    let raw = raw_arguments.next()?;
12511    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
12512        return None;
12513    }
12514
12515    let left = raw.child_by_field_name("left")?;
12516    if left.is_missing() || left.start_byte() == left.end_byte() {
12517        return None;
12518    }
12519    let right = raw.child_by_field_name("right")?;
12520    if right.kind() != "compound_literal_expression"
12521        || right.is_missing()
12522        || right
12523            .child_by_field_name("type")
12524            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
12525        || right
12526            .child_by_field_name("value")
12527            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
12528    {
12529        return None;
12530    }
12531    let has_intervening_error = (0..raw.child_count())
12532        .filter_map(|index| raw.child(index))
12533        .any(|child| {
12534            child.kind() == "ERROR"
12535                && !child.is_missing()
12536                && child.start_byte() >= left.end_byte()
12537                && child.end_byte() <= right.start_byte()
12538        });
12539    has_intervening_error.then_some((raw, left, right))
12540}
12541
12542pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
12543    match node.kind() {
12544        "new_expression" => node
12545            .child_by_field_name("type")
12546            .or_else(|| node.named_child(0)),
12547        "compound_literal_expression" => node.child_by_field_name("type"),
12548        "call_expression" => node.child_by_field_name("function"),
12549        _ => None,
12550    }
12551}
12552
12553/// The structured type named by a C-style cast expression.
12554///
12555/// Tree-sitter wraps the actual type syntax in a `type_descriptor`.  Return its
12556/// structured `type` field so resolution retains qualified and nested syntax
12557/// without reparsing source text.
12558pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
12559    if node.kind() != "cast_expression" {
12560        return None;
12561    }
12562    let descriptor = node.child_by_field_name("type")?;
12563    if descriptor.kind() == "type_descriptor" {
12564        descriptor.child_by_field_name("type")
12565    } else {
12566        Some(descriptor)
12567    }
12568}
12569
12570/// The expression a field access reads its member from.
12571///
12572/// That is normally the node in the `argument` field. A preprocessor
12573/// conditional inside a call's argument list defeats the grammar: tree-sitter
12574/// ends the argument list at the `#if` line, then reads the text that follows
12575/// `#endif` as a field access on the whole call expression and leaves the real
12576/// receiver in an `ERROR` node between the `argument` child and the operator.
12577/// mbedtls writes exactly that at `library/ssl_tls.c:4999`, where
12578/// `ssl->session->encrypt_then_mac` is one conditionally compiled call argument
12579/// (#2982).
12580///
12581/// `.` and `->` read whatever ends immediately before them in either tree, so
12582/// answer from the operator's position instead of trusting the `argument`
12583/// field, and unwrap the single expression a displaced `ERROR` holds.
12584pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
12585    debug_assert_eq!(field.kind(), "field_expression");
12586    let operator = field.child_by_field_name("operator")?;
12587    let mut cursor = field.walk();
12588    let receiver = field
12589        .named_children(&mut cursor)
12590        .filter(|child| child.end_byte() <= operator.start_byte())
12591        .last()?;
12592    if receiver.kind() != "ERROR" {
12593        return Some(receiver);
12594    }
12595    (receiver.named_child_count() == 1)
12596        .then(|| receiver.named_child(0))
12597        .flatten()
12598}
12599
12600pub fn field_initializer_constructs_target(
12601    node: Node<'_>,
12602    ctx: &ScanCtx<'_>,
12603    owner: &CodeUnit,
12604) -> bool {
12605    // A qualified name in a constructor initializer denotes a base
12606    // subobject constructor (`namespace::Base(args)`), not a member field.  The
12607    // field-initializer grammar exposes the qualified name as one structured
12608    // `qualified_identifier`; resolve its owner through the same lexical type
12609    // machinery used for ordinary C++ type references before considering the
12610    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
12611    // qualified non-constructor member, and an unresolved owner out of the
12612    // target constructor's inverse usage set.
12613    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
12614        return qualified_base_initializer_constructs_target(node, ctx, owner);
12615    }
12616    let Some(name) = node
12617        .child_by_field_name("name")
12618        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
12619        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
12620    else {
12621        return false;
12622    };
12623    let field_name = node_text(name, ctx.source);
12624    ctx.visibility
12625        .visible_identifier_candidates(ctx.file, field_name)
12626        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
12627        .any(|unit| field_declares_type(unit, ctx, owner))
12628}
12629
12630fn qualified_base_initializer_constructs_target(
12631    node: Node<'_>,
12632    ctx: &ScanCtx<'_>,
12633    owner: &CodeUnit,
12634) -> bool {
12635    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
12636        return false;
12637    };
12638    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
12639        return false;
12640    };
12641    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
12642        return false;
12643    };
12644    let resolves_target = |components: &[String]| {
12645        matches!(
12646            ctx.visibility.resolve_type_components_lexically_for_target(
12647                &ctx.analyzer,
12648                ctx.file,
12649                components,
12650                is_globally_qualified_cpp_name(qualified),
12651                &lexical_scope,
12652                owner,
12653            ),
12654            LexicalTypeResolution::Resolved { unit, .. }
12655                if same_visible_symbol(&unit, owner)
12656        )
12657    };
12658    if resolves_target(&components) {
12659        return true;
12660    }
12661
12662    // Some real-world code spells a base mem-initializer as
12663    // `Base::Base(args)`. In that structured path the final component repeats
12664    // the constructor name; resolve the preceding type path. The terminal
12665    // identity check prevents an arbitrary qualified member from taking this
12666    // route.
12667    components
12668        .last()
12669        .is_some_and(|terminal| terminal == owner.identifier())
12670        && resolves_target(&components[..components.len() - 1])
12671}
12672
12673fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
12674    unit.signature()
12675        .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
12676        || ctx
12677            .analyzer
12678            .get_source(unit, false)
12679            .is_some_and(|declaration| {
12680                field_declaration_type_matches(&declaration, unit, ctx, owner)
12681            })
12682}
12683
12684pub fn field_declared_binding(
12685    analyzer: &CppGraphSource<'_>,
12686    visibility: &VisibilityIndex<'_>,
12687    visible_from: &ProjectFile,
12688    field: &CodeUnit,
12689) -> Option<CppScanBinding> {
12690    let fact = visibility.field_declared_type_fact(analyzer, field)?;
12691    let normalized = normalize_field_type_text(&fact.type_text);
12692    let resolved = visibility.resolve_unique_canonical_type_for_declaration(
12693        analyzer,
12694        visible_from,
12695        field,
12696        &normalized,
12697    );
12698    let resolved = match (resolved, fact.template_arguments.as_deref()) {
12699        (Some(primary), Some(arguments)) => visibility
12700            .resolve_template_arguments(visible_from, primary, arguments)
12701            .ok(),
12702        (resolved, None) => resolved,
12703        (None, Some(_)) => None,
12704    }
12705    .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
12706    Some(CppScanBinding::from_type_name(
12707        normalized,
12708        resolved,
12709        fact.indirection,
12710    ))
12711}
12712
12713/// Resolve the receiver type minted for a named declarator on an anonymous C
12714/// aggregate, such as `struct { int r; } c`. The declaration index preserves
12715/// the aggregate as the nested class `Owner$c`, but the field's type fact has
12716/// no spelling that can name that class. Confirm the anonymous aggregate from
12717/// its parsed declaration, then use the owner's structured child relationship
12718/// to recover the one corresponding receiver type.
12719fn anonymous_aggregate_field_owner(
12720    analyzer: &CppGraphSource<'_>,
12721    visibility: &VisibilityIndex<'_>,
12722    visible_from: &ProjectFile,
12723    field: &CodeUnit,
12724) -> Option<CodeUnit> {
12725    let owner = type_owner_of(analyzer, field)?;
12726    if !owner.is_class() {
12727        return None;
12728    }
12729    let declaration = analyzer.get_source(field, false)?;
12730    let mut parser = Parser::new();
12731    parser
12732        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12733        .ok()?;
12734    let tree = parser.parse(&declaration, None)?;
12735    let mut stack = vec![tree.root_node()];
12736    while let Some(node) = stack.pop() {
12737        if matches!(node.kind(), "declaration" | "field_declaration")
12738            && let Some(type_node) = node
12739                .child_by_field_name("type")
12740                .or_else(|| first_type_child(node))
12741            && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
12742            && type_node.child_by_field_name("name").is_none()
12743            && declared_name_indirection(node, type_node, field.identifier(), &declaration)
12744                .is_some()
12745        {
12746            let matches = visibility
12747                .visible_members_for_owner_name(visible_from, &owner, field.identifier())
12748                .into_iter()
12749                .filter(|child| child.is_class() && child.identifier() == field.identifier())
12750                .collect::<Vec<_>>();
12751            return match matches.as_slice() {
12752                [child] => Some((*child).clone()),
12753                _ => None,
12754            };
12755        }
12756        let mut cursor = node.walk();
12757        stack.extend(node.named_children(&mut cursor));
12758    }
12759    None
12760}
12761
12762/// Resolve an anonymous C aggregate's generated owner from its declaration
12763/// range. Anonymous local structs and unions have no type name to enter into
12764/// the visibility index; declaration extraction gives them a structured class
12765/// identity keyed by the aggregate node's exact CST range instead. Matching
12766/// that range keeps nested aggregates and unrelated generated owners out of
12767/// the result without inspecting source text.
12768pub fn anonymous_aggregate_owner(
12769    analyzer: &CppGraphSource<'_>,
12770    file: &ProjectFile,
12771    node: Node<'_>,
12772) -> Option<CodeUnit> {
12773    if !matches!(node.kind(), "struct_specifier" | "union_specifier")
12774        || node.child_by_field_name("name").is_some()
12775    {
12776        return None;
12777    }
12778    let mut candidates = analyzer
12779        .declarations(file)
12780        .into_iter()
12781        .filter(|candidate| {
12782            candidate.is_class()
12783                && analyzer.ranges(candidate).into_iter().any(|range| {
12784                    range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
12785                })
12786        })
12787        .collect::<Vec<_>>();
12788    candidates.sort_by_key(|candidate| candidate.fq_name());
12789    candidates.dedup();
12790    match candidates.as_slice() {
12791        [candidate] => Some(candidate.clone()),
12792        _ => None,
12793    }
12794}
12795
12796/// The one logical type the candidates name, or why they do not name one.
12797fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
12798    let Some(first) = candidates.first() else {
12799        return Err(TypeCandidateFailure::Unresolvable);
12800    };
12801    if candidates
12802        .iter()
12803        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
12804    {
12805        Ok((*first).clone())
12806    } else {
12807        Err(TypeCandidateFailure::Ambiguous)
12808    }
12809}
12810
12811fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
12812    logical_type_candidate(candidates).ok()
12813}
12814
12815fn unique_type_candidate_preserving_alias(
12816    analyzer: &CppGraphSource<'_>,
12817    file: &ProjectFile,
12818    candidates: &[&CodeUnit],
12819) -> Option<CodeUnit> {
12820    let first = *candidates.first()?;
12821    if declared_type_alias(analyzer, first) {
12822        return candidates
12823            .iter()
12824            .all(|candidate| {
12825                declared_type_alias(analyzer, candidate)
12826                    && candidate.kind() == first.kind()
12827                    && candidate.fq_name() == first.fq_name()
12828                    && candidate.source() == first.source()
12829            })
12830            .then(|| first.clone());
12831    }
12832    if analyzer.reference_uses_c_semantics(file)
12833        && first.is_class()
12834        && indexed_c_tag_kind(analyzer, first).is_some()
12835    {
12836        let mut full_source = None;
12837        let mut tag_kind = None;
12838        for candidate in candidates.iter().copied() {
12839            let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
12840            if tag_kind
12841                .replace(candidate_tag_kind)
12842                .is_some_and(|existing| existing != candidate_tag_kind)
12843            {
12844                return None;
12845            }
12846            if cpp_class_declaration_strength(analyzer, candidate)
12847                == CppClassDeclarationStrength::Full
12848                && full_source
12849                    .replace(candidate.source())
12850                    .is_some_and(|existing| existing != candidate.source())
12851            {
12852                return None;
12853            }
12854        }
12855    }
12856    candidates
12857        .iter()
12858        .all(|candidate| {
12859            !declared_type_alias(analyzer, candidate)
12860                && candidate.kind() == first.kind()
12861                && candidate.fq_name() == first.fq_name()
12862        })
12863        .then(|| first.clone())
12864}
12865
12866fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
12867    is_type_alias(unit)
12868        || analyzer
12869            .type_alias_provider()
12870            .is_some_and(|provider| provider.is_type_alias(unit))
12871}
12872
12873pub fn field_declared_type_binding(
12874    analyzer: &CppGraphSource<'_>,
12875    visibility: &VisibilityIndex<'_>,
12876    visible_from: &ProjectFile,
12877    field: &CodeUnit,
12878) -> Option<(String, Option<CodeUnit>, i32)> {
12879    let fact = visibility.field_declared_type_fact(analyzer, field)?;
12880    let normalized = normalize_field_type_text(&fact.type_text);
12881    let primary = visibility.resolve_unique_canonical_type_for_declaration(
12882        analyzer,
12883        visible_from,
12884        field,
12885        &normalized,
12886    );
12887    let resolved = match (primary, fact.template_arguments.as_deref()) {
12888        (Some(primary), Some(arguments)) => visibility
12889            .resolve_template_arguments(visible_from, primary, arguments)
12890            .ok(),
12891        (resolved, None) => resolved,
12892        (None, Some(_)) => None,
12893    };
12894    Some((normalized, resolved, fact.indirection))
12895}
12896
12897fn decode_field_declared_type_fact(
12898    analyzer: &CppGraphSource<'_>,
12899    field: &CodeUnit,
12900) -> Option<DeclaredFieldTypeFact> {
12901    let Some(declaration) = analyzer.get_source(field, false) else {
12902        return decode_indexed_field_declared_type_fact(analyzer, field);
12903    };
12904    let mut parser = Parser::new();
12905    parser
12906        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12907        .ok()?;
12908    // A field's indexed source is stored without its surrounding class body.
12909    // Give tree-sitter that grammatical context before checking recovery-only
12910    // field shapes such as `PyObject_HEAD Imaging image;`.
12911    let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
12912    let contextual_tree = parser.parse(&contextual_declaration, None)?;
12913    let mut stack = vec![contextual_tree.root_node()];
12914    while let Some(node) = stack.pop() {
12915        if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
12916            && node_text(recovered.name, &contextual_declaration) == field.identifier()
12917        {
12918            return Some(DeclaredFieldTypeFact {
12919                type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
12920                indirection: recovered.pointer_depth(),
12921                template_arguments: None,
12922            });
12923        }
12924        if let Some(recovered) =
12925            recovered_function_like_field_declarator(node, &contextual_declaration)
12926            && node_text(recovered.name, &contextual_declaration) == field.identifier()
12927        {
12928            let type_node = node
12929                .child_by_field_name("type")
12930                .or_else(|| first_type_child(node))?;
12931            return Some(DeclaredFieldTypeFact {
12932                type_text: node_text(type_node, &contextual_declaration).to_string(),
12933                indirection: recovered.pointer_depth(),
12934                template_arguments: cpp_template_reference_arguments(
12935                    type_node,
12936                    &contextual_declaration,
12937                ),
12938            });
12939        }
12940        if let Some(fact) =
12941            decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
12942        {
12943            return Some(fact);
12944        }
12945        let mut cursor = node.walk();
12946        stack.extend(node.named_children(&mut cursor));
12947    }
12948    let tree = parser.parse(&declaration, None)?;
12949    let mut stack = vec![tree.root_node()];
12950    while let Some(node) = stack.pop() {
12951        if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
12952        {
12953            return Some(fact);
12954        }
12955        let mut cursor = node.walk();
12956        stack.extend(node.named_children(&mut cursor));
12957    }
12958    None
12959}
12960
12961/// Decode a field whose generated owner prevents the ordinary source lookup
12962/// from selecting a standalone declaration. The indexed ranges still point
12963/// into the physical syntax tree, so recover the enclosing declaration from
12964/// that structure and apply the same declarator decoder to it.
12965fn decode_indexed_field_declared_type_fact(
12966    analyzer: &CppGraphSource<'_>,
12967    field: &CodeUnit,
12968) -> Option<DeclaredFieldTypeFact> {
12969    let cpp = analyzer.cpp?;
12970    let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
12971    let source = prepared.source();
12972    let root = prepared.tree().root_node();
12973    for range in analyzer.ranges(field) {
12974        let end = range.start_byte.saturating_add(1).min(source.len());
12975        let mut current = root.descendant_for_byte_range(range.start_byte, end);
12976        while let Some(node) = current {
12977            if matches!(node.kind(), "declaration" | "field_declaration")
12978                && let Some(fact) =
12979                    decode_declared_field_type_node(node, field.identifier(), source)
12980            {
12981                return Some(fact);
12982            }
12983            current = node.parent();
12984        }
12985    }
12986    None
12987}
12988
12989fn decode_declared_field_type_node(
12990    node: Node<'_>,
12991    field_name: &str,
12992    source: &str,
12993) -> Option<DeclaredFieldTypeFact> {
12994    if !matches!(node.kind(), "declaration" | "field_declaration") {
12995        return None;
12996    }
12997    let type_node = node
12998        .child_by_field_name("type")
12999        .or_else(|| first_type_child(node))?;
13000    let indirection = declared_name_indirection(node, type_node, field_name, source)?;
13001    let declared_type = if matches!(
13002        type_node.kind(),
13003        "class_specifier" | "struct_specifier" | "union_specifier"
13004    ) {
13005        type_node.child_by_field_name("name")
13006    } else {
13007        Some(type_node)
13008    };
13009    Some(DeclaredFieldTypeFact {
13010        type_text: declared_type.map_or_else(
13011            || field_name.to_string(),
13012            |declared_type| node_text(declared_type, source).to_string(),
13013        ),
13014        indirection,
13015        template_arguments: declared_type
13016            .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
13017    })
13018}
13019
13020/// Text of the type that a C or C++ alias declaration names, read from the
13021/// `type_definition` or `alias_declaration` node's `type` field.
13022///
13023/// The declaration text is never scanned. A function-pointer typedef
13024/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
13025/// so no prefix or suffix of the spelling isolates the target.
13026///
13027/// An alias whose declarator is a function declarator names a function type:
13028/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
13029/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
13030/// so such an alias has no canonical target. Its `type` field holds the return
13031/// type `R`, which is a different type from the alias, so this returns `None`
13032/// rather than that return type.
13033pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
13034    let mut parser = Parser::new();
13035    parser
13036        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13037        .ok()?;
13038    let tree = parser.parse(declaration, None)?;
13039    let mut stack = vec![tree.root_node()];
13040    while let Some(node) = stack.pop() {
13041        let type_node = match node.kind() {
13042            "type_definition" => {
13043                let mut cursor = node.walk();
13044                if node
13045                    .children_by_field_name("declarator", &mut cursor)
13046                    .any(declarator_names_function_type)
13047                {
13048                    return None;
13049                }
13050                node.child_by_field_name("type")?
13051            }
13052            "alias_declaration" => {
13053                let type_node = node.child_by_field_name("type")?;
13054                if type_node
13055                    .child_by_field_name("declarator")
13056                    .is_some_and(declarator_names_function_type)
13057                {
13058                    return None;
13059                }
13060                type_node
13061            }
13062            _ => {
13063                let mut cursor = node.walk();
13064                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13065                stack.extend(children.into_iter().rev());
13066                continue;
13067            }
13068        };
13069        return Some(node_text(type_node, declaration).to_string());
13070    }
13071    None
13072}
13073
13074/// Whether an alias declaration's own declarator adds indirection that
13075/// [`cpp_alias_declaration_target_text`] does not report.
13076///
13077/// That function reads the declaration's `type` field, where `typedef Foo *Bar`
13078/// keeps only `Foo`: the `*` lives in the sibling declarator. Substituting such
13079/// an alias would equate `f(Bar)` with `f(Foo)`, so a comparison that cannot
13080/// prove the alias adds no indirection must refuse to follow it. A declaration
13081/// this cannot read at all is refused for the same reason.
13082fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
13083    let mut parser = Parser::new();
13084    if parser
13085        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13086        .is_err()
13087    {
13088        return true;
13089    }
13090    let Some(tree) = parser.parse(declaration, None) else {
13091        return true;
13092    };
13093    let mut stack = vec![tree.root_node()];
13094    while let Some(node) = stack.pop() {
13095        let declarators = match node.kind() {
13096            "type_definition" => {
13097                let mut cursor = node.walk();
13098                node.children_by_field_name("declarator", &mut cursor)
13099                    .collect::<Vec<_>>()
13100            }
13101            "alias_declaration" => node
13102                .child_by_field_name("type")
13103                .and_then(|type_node| type_node.child_by_field_name("declarator"))
13104                .into_iter()
13105                .collect::<Vec<_>>(),
13106            _ => {
13107                let mut cursor = node.walk();
13108                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13109                stack.extend(children.into_iter().rev());
13110                continue;
13111            }
13112        };
13113        return declarators.into_iter().any(cpp_declarator_adds_indirection);
13114    }
13115    true
13116}
13117
13118/// True when an alias declarator names a function type.
13119///
13120/// The declarator chain is walked through the `declarator` field, so the
13121/// parameter list -- a sibling field -- is never entered and a parameter's own
13122/// function declarator cannot be mistaken for the alias's.
13123fn declarator_names_function_type(declarator: Node<'_>) -> bool {
13124    let mut current = Some(declarator);
13125    while let Some(node) = current {
13126        match node.kind() {
13127            "function_declarator" | "abstract_function_declarator" => return true,
13128            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
13129                current = node.named_child(0);
13130            }
13131            _ => current = node.child_by_field_name("declarator"),
13132        }
13133    }
13134    false
13135}
13136
13137/// Whether one indexed field declaration is a function or function-pointer
13138/// value. This follows tree-sitter declarator fields and never infers
13139/// callability from source spelling.
13140pub fn cpp_field_declaration_names_function_type(declaration: &str, field_name: &str) -> bool {
13141    let mut parser = Parser::new();
13142    if parser
13143        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13144        .is_err()
13145    {
13146        return false;
13147    }
13148    let Some(tree) = parser.parse(declaration, None) else {
13149        return false;
13150    };
13151    let mut stack = vec![tree.root_node()];
13152    while let Some(node) = stack.pop() {
13153        if matches!(node.kind(), "declaration" | "field_declaration") {
13154            let mut cursor = node.walk();
13155            if node
13156                .children_by_field_name("declarator", &mut cursor)
13157                .any(|declarator| {
13158                    declarator_name_node(declarator).is_some_and(|name| {
13159                        node_text(name, declaration) == field_name
13160                            && declarator_names_function_type(declarator)
13161                    })
13162                })
13163            {
13164                return true;
13165            }
13166        }
13167        let mut cursor = node.walk();
13168        stack.extend(node.named_children(&mut cursor));
13169    }
13170    false
13171}
13172
13173/// Whether one indexed alias declaration names a function or function-pointer
13174/// type. The alias name is matched through the declarator field so a function
13175/// type used by a parameter cannot be mistaken for the alias itself.
13176pub fn cpp_alias_declaration_names_function_type(declaration: &str, alias_name: &str) -> bool {
13177    let mut parser = Parser::new();
13178    if parser
13179        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13180        .is_err()
13181    {
13182        return false;
13183    }
13184    let Some(tree) = parser.parse(declaration, None) else {
13185        return false;
13186    };
13187    let mut stack = vec![tree.root_node()];
13188    while let Some(node) = stack.pop() {
13189        match node.kind() {
13190            "type_definition" => {
13191                let mut cursor = node.walk();
13192                if node
13193                    .children_by_field_name("declarator", &mut cursor)
13194                    .any(|declarator| {
13195                        extract_typedef_declarator_name(declarator, declaration)
13196                            .is_some_and(|name| name == alias_name)
13197                            && declarator_names_function_type(declarator)
13198                    })
13199                {
13200                    return true;
13201                }
13202            }
13203            "alias_declaration" => {
13204                let names_alias = node
13205                    .child_by_field_name("name")
13206                    .is_some_and(|name| node_text(name, declaration) == alias_name);
13207                if names_alias
13208                    && node
13209                        .child_by_field_name("type")
13210                        .and_then(|type_node| type_node.child_by_field_name("declarator"))
13211                        .is_some_and(declarator_names_function_type)
13212                {
13213                    return true;
13214                }
13215            }
13216            _ => {}
13217        }
13218        let mut cursor = node.walk();
13219        stack.extend(node.named_children(&mut cursor));
13220    }
13221    false
13222}
13223
13224fn decode_structured_alias_target(
13225    analyzer: &CppGraphSource<'_>,
13226    unit: &CodeUnit,
13227) -> Option<StructuredAliasTarget> {
13228    analyzer
13229        .get_source(unit, false)
13230        .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
13231        .or_else(|| {
13232            let signature = unit.signature()?;
13233            decode_structured_alias_target_source(unit, signature, false)
13234        })
13235}
13236
13237fn decode_structured_alias_target_source(
13238    unit: &CodeUnit,
13239    declaration: &str,
13240    require_top_level: bool,
13241) -> Option<StructuredAliasTarget> {
13242    let mut parser = Parser::new();
13243    parser
13244        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13245        .ok()?;
13246    let tree = parser.parse(declaration, None)?;
13247    let mut stack = vec![tree.root_node()];
13248    while let Some(node) = stack.pop() {
13249        let type_node = match node.kind() {
13250            "type_definition" => {
13251                if require_top_level
13252                    && node
13253                        .parent()
13254                        .is_none_or(|parent| parent.kind() != "translation_unit")
13255                {
13256                    let mut cursor = node.walk();
13257                    stack.extend(node.named_children(&mut cursor));
13258                    continue;
13259                }
13260                let mut declarator_cursor = node.walk();
13261                let declarator = node
13262                    .children_by_field_name("declarator", &mut declarator_cursor)
13263                    .find(|declarator| {
13264                        extract_typedef_declarator_name(*declarator, declaration)
13265                            .is_some_and(|name| name == unit.identifier())
13266                    })?;
13267                if declarator_names_function_type(declarator) {
13268                    return None;
13269                }
13270                node.child_by_field_name("type")?
13271            }
13272            "alias_declaration" => {
13273                if require_top_level
13274                    && node
13275                        .parent()
13276                        .is_none_or(|parent| parent.kind() != "translation_unit")
13277                {
13278                    let mut cursor = node.walk();
13279                    stack.extend(node.named_children(&mut cursor));
13280                    continue;
13281                }
13282                let name = node.child_by_field_name("name")?;
13283                if node_text(name, declaration) != unit.identifier() {
13284                    return None;
13285                }
13286                let type_node = node.child_by_field_name("type")?;
13287                if type_node
13288                    .child_by_field_name("declarator")
13289                    .is_some_and(declarator_names_function_type)
13290                {
13291                    return None;
13292                }
13293                type_node
13294            }
13295            _ => {
13296                let mut cursor = node.walk();
13297                stack.extend(node.named_children(&mut cursor));
13298                continue;
13299            }
13300        };
13301        return structured_alias_type_target(type_node, declaration);
13302    }
13303    None
13304}
13305
13306fn structured_alias_type_target(
13307    mut type_node: Node<'_>,
13308    source: &str,
13309) -> Option<StructuredAliasTarget> {
13310    while type_node.kind() == "type_descriptor" {
13311        type_node = type_node.child_by_field_name("type")?;
13312    }
13313    if type_node.kind() == "primitive_type" {
13314        return Some(StructuredAliasTarget::Builtin);
13315    }
13316    if matches!(
13317        type_node.kind(),
13318        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13319    ) {
13320        type_node = type_node.child_by_field_name("name")?;
13321    }
13322    let global = type_node.child_by_field_name("scope").is_none()
13323        && type_node.child(0).is_some_and(|child| child.kind() == "::");
13324    let mut components = Vec::new();
13325    append_structured_type_components(type_node, source, &mut components)?;
13326    let arguments = cpp_template_reference_arguments(type_node, source);
13327    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
13328        components,
13329        global,
13330        arguments,
13331    })
13332}
13333
13334fn append_structured_type_components(
13335    node: Node<'_>,
13336    source: &str,
13337    out: &mut Vec<String>,
13338) -> Option<()> {
13339    match node.kind() {
13340        "identifier" | "namespace_identifier" | "type_identifier" => {
13341            out.push(node_text(node, source).to_string());
13342            Some(())
13343        }
13344        "template_type" => {
13345            append_structured_type_components(node.child_by_field_name("name")?, source, out)
13346        }
13347        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13348            if let Some(scope) = node.child_by_field_name("scope") {
13349                append_structured_type_components(scope, source, out)?;
13350            }
13351            append_structured_type_components(node.child_by_field_name("name")?, source, out)
13352        }
13353        _ => None,
13354    }
13355}
13356
13357pub(crate) fn declared_name_indirection(
13358    declaration: Node<'_>,
13359    type_node: Node<'_>,
13360    field_name: &str,
13361    source: &str,
13362) -> Option<i32> {
13363    let mut stack = Vec::new();
13364    let mut cursor = declaration.walk();
13365    stack.extend(
13366        declaration
13367            .named_children(&mut cursor)
13368            .filter(|child| !same_node(*child, type_node)),
13369    );
13370    while let Some(node) = stack.pop() {
13371        if matches!(node.kind(), "identifier" | "field_identifier")
13372            && node_text(node, source) == field_name
13373        {
13374            let mut indirection = 0;
13375            let mut current = node.parent();
13376            while let Some(parent) = current {
13377                if same_node(parent, declaration) {
13378                    return Some(indirection);
13379                }
13380                if parent.kind() == "pointer_declarator" {
13381                    indirection += 1;
13382                }
13383                current = parent.parent();
13384            }
13385            return None;
13386        }
13387        let mut cursor = node.walk();
13388        stack.extend(node.named_children(&mut cursor));
13389    }
13390    None
13391}
13392
13393fn field_declaration_type_matches(
13394    declaration: &str,
13395    unit: &CodeUnit,
13396    ctx: &ScanCtx<'_>,
13397    owner: &CodeUnit,
13398) -> bool {
13399    ctx.visibility
13400        .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
13401        || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
13402            let normalized = normalize_field_type_text(type_text);
13403            ctx.visibility
13404                .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
13405                || ctx.visibility.resolves_to_type(
13406                    &ctx.analyzer,
13407                    ctx.file,
13408                    normalized.as_str(),
13409                    owner,
13410                )
13411        })
13412}
13413
13414fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
13415    let declaration = declaration
13416        .split(['=', ';'])
13417        .next()
13418        .unwrap_or(declaration)
13419        .trim();
13420    let index = declaration.rfind(field_name)?;
13421    let before = &declaration[..index];
13422    let after = &declaration[index + field_name.len()..];
13423    if before.chars().next_back().is_some_and(is_identifier_char)
13424        || after.chars().next().is_some_and(is_identifier_char)
13425    {
13426        return None;
13427    }
13428    Some(before.trim())
13429}
13430
13431fn normalize_field_type_text(type_text: &str) -> String {
13432    const FIELD_SPECIFIERS: [&str; 8] = [
13433        "extern ",
13434        "static ",
13435        "mutable ",
13436        "constexpr ",
13437        "constinit ",
13438        "inline ",
13439        "volatile ",
13440        "const ",
13441    ];
13442
13443    let mut normalized = normalize_type_text(type_text);
13444    loop {
13445        let Some(stripped) = FIELD_SPECIFIERS
13446            .iter()
13447            .find_map(|specifier| normalized.strip_prefix(specifier))
13448        else {
13449            return normalized;
13450        };
13451        normalized = normalize_type_text(stripped);
13452    }
13453}
13454
13455fn is_identifier_char(ch: char) -> bool {
13456    ch == '_' || ch.is_ascii_alphanumeric()
13457}
13458
13459pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13460    let Some(type_node) = node.child_by_field_name("type") else {
13461        return false;
13462    };
13463    ctx.visibility.resolves_to_type(
13464        &ctx.analyzer,
13465        ctx.file,
13466        node_text(type_node, ctx.source),
13467        owner,
13468    )
13469}
13470
13471pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13472    !ctx.analyzer
13473        .declarations(ctx.file)
13474        .into_iter()
13475        .filter(|unit| unit.is_function())
13476        .any(|unit| {
13477            ctx.analyzer.ranges(&unit).iter().any(|range| {
13478                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13479            })
13480        })
13481}
13482
13483/// How a `T var ...;` declaration initializes its object.
13484pub enum DeclarationConstructorInitializer<'tree> {
13485    /// Direct initialization, `T var(args)` or `T var{args}`: the argument list
13486    /// the declaration hands the constructor.
13487    Arguments(Node<'tree>),
13488    /// Copy initialization from one expression, `T var = expr`, which supplies a
13489    /// single constructor argument without spelling an argument list.
13490    Expression(Node<'tree>),
13491    /// `T var;`, which names no constructor argument at all.
13492    Empty,
13493}
13494
13495pub fn declaration_constructor_initializer(
13496    node: Node<'_>,
13497) -> DeclarationConstructorInitializer<'_> {
13498    let mut cursor = node.walk();
13499    for child in node.named_children(&mut cursor) {
13500        if child.kind() == "init_declarator" {
13501            let Some(value) = child
13502                .child_by_field_name("value")
13503                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13504                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13505            else {
13506                return DeclarationConstructorInitializer::Empty;
13507            };
13508            return match value.kind() {
13509                "argument_list" | "initializer_list" => {
13510                    DeclarationConstructorInitializer::Arguments(value)
13511                }
13512                "compound_literal_expression" => call_arguments_node(value)
13513                    .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13514                        DeclarationConstructorInitializer::Arguments(arguments)
13515                    }),
13516                _ => DeclarationConstructorInitializer::Expression(value),
13517            };
13518        }
13519        if let Some(declarator) = declaration_declarator(node, child) {
13520            return declarator_parameters(declarator)
13521                .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13522                    DeclarationConstructorInitializer::Arguments(parameters)
13523                });
13524        }
13525    }
13526    DeclarationConstructorInitializer::Empty
13527}
13528
13529pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13530    match declaration_constructor_initializer(node) {
13531        DeclarationConstructorInitializer::Arguments(arguments) => {
13532            argument_children(arguments).count()
13533        }
13534        DeclarationConstructorInitializer::Expression(_) => 1,
13535        DeclarationConstructorInitializer::Empty => 0,
13536    }
13537}
13538
13539/// The parameter list of the innermost declarator, which is where a
13540/// `T var(args)` declaration parsed as a function declarator keeps the
13541/// constructor arguments.
13542fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13543    let mut current = node;
13544    loop {
13545        if let Some(parameters) = current.child_by_field_name("parameters") {
13546            return Some(parameters);
13547        }
13548        current = current.child_by_field_name("declarator")?;
13549    }
13550}
13551
13552pub(super) fn first_named_child_of_kind<'tree>(
13553    node: Node<'tree>,
13554    kind: &str,
13555) -> Option<Node<'tree>> {
13556    let mut cursor = node.walk();
13557    node.named_children(&mut cursor)
13558        .find(|child| child.kind() == kind)
13559}
13560
13561fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13562    let mut stack = vec![root];
13563    while let Some(node) = stack.pop() {
13564        if node.kind() == kind {
13565            return Some(node);
13566        }
13567        push_named_children_reversed(node, &mut stack);
13568    }
13569    None
13570}
13571
13572fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13573    if node.kind() == "identifier" {
13574        return true;
13575    }
13576    if node.kind() == "parenthesized_expression" {
13577        return false;
13578    }
13579    if node.kind() == "call_expression" {
13580        return node
13581            .child_by_field_name("function")
13582            .is_some_and(|function| function.kind() == "identifier");
13583    }
13584    let mut stack = vec![node];
13585    while let Some(descendant) = stack.pop() {
13586        if descendant != node && descendant.kind() == "parenthesized_expression" {
13587            continue;
13588        }
13589        if descendant.kind() == "identifier" {
13590            return true;
13591        }
13592        if descendant.kind() == "call_expression" {
13593            if descendant
13594                .child_by_field_name("function")
13595                .is_some_and(|function| function.kind() == "identifier")
13596            {
13597                return true;
13598            }
13599            continue;
13600        }
13601        push_named_children_reversed(descendant, &mut stack);
13602    }
13603    false
13604}
13605
13606fn macro_expansion_shape_is_safe(
13607    node: Node<'_>,
13608    source: &str,
13609    parameters: &[String],
13610    environment: &MacroEnvironment,
13611) -> bool {
13612    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
13613        return true;
13614    }
13615    if node.kind() == "call_expression" {
13616        let Some(function) = node.child_by_field_name("function") else {
13617            return true;
13618        };
13619        if function.kind() != "identifier" {
13620            return true;
13621        }
13622        let function_name = node_text(function, source);
13623        if parameters
13624            .iter()
13625            .any(|parameter| parameter == function_name)
13626        {
13627            return false;
13628        }
13629        if !environment.may_bind(function_name) {
13630            return true;
13631        }
13632        let Some(arguments) = node.child_by_field_name("arguments") else {
13633            return false;
13634        };
13635        return argument_children(arguments).all(|argument| {
13636            if argument.kind() == "identifier"
13637                && parameters
13638                    .iter()
13639                    .any(|parameter| parameter == node_text(argument, source))
13640            {
13641                return false;
13642            }
13643            macro_expansion_shape_is_safe(argument, source, parameters, environment)
13644        });
13645    }
13646    let mut stack = vec![node];
13647    while let Some(descendant) = stack.pop() {
13648        if descendant != node {
13649            if descendant.kind() == "parenthesized_expression" {
13650                continue;
13651            }
13652            if descendant.kind() == "call_expression" {
13653                let expands = descendant
13654                    .child_by_field_name("function")
13655                    .filter(|function| function.kind() == "identifier")
13656                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
13657                if expands {
13658                    return false;
13659                }
13660                continue;
13661            }
13662        }
13663        if descendant.kind() == "identifier" {
13664            let identifier = node_text(descendant, source);
13665            if parameters.iter().any(|parameter| parameter == identifier)
13666                || environment.may_bind(identifier)
13667            {
13668                return false;
13669            }
13670        }
13671        push_named_children_reversed(descendant, &mut stack);
13672    }
13673    true
13674}
13675
13676fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
13677    let text = node_text(path, source);
13678    match path.kind() {
13679        "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
13680        "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
13681        _ => None,
13682    }
13683}
13684
13685fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
13686    let source = prepared.source();
13687    let mut facts = Vec::new();
13688    let mut nodes = vec![prepared.tree().root_node()];
13689    while let Some(node) = nodes.pop() {
13690        if node.kind() == "preproc_include" {
13691            let Some(path) = node
13692                .child_by_field_name("path")
13693                .and_then(|path| structured_include_path(path, source))
13694                .map(str::to_owned)
13695            else {
13696                continue;
13697            };
13698            facts.push(StructuredIncludeFact {
13699                start_byte: node.start_byte(),
13700                end_byte: node.end_byte(),
13701                path,
13702            });
13703            continue;
13704        }
13705        push_named_children_reversed(node, &mut nodes);
13706    }
13707    Arc::from(facts.into_boxed_slice())
13708}
13709
13710fn has_unresolved_include_visible_before_in_prepared(
13711    file: &ProjectFile,
13712    prepared: &PreparedSyntaxTree,
13713    include_targets: &IncludeTargetIndex,
13714    facts: &[StructuredIncludeFact],
13715    before_byte: usize,
13716) -> bool {
13717    let guards = OnceCell::new();
13718    let reference = CallableReferenceContext {
13719        file,
13720        position: Some(CallableReferencePosition {
13721            prepared,
13722            byte: before_byte,
13723            guards: &guards,
13724        }),
13725    };
13726    let root = prepared.tree().root_node();
13727    facts
13728        .iter()
13729        .filter(|fact| fact.end_byte <= before_byte)
13730        .any(|fact| {
13731            let node = root
13732                .descendant_for_byte_range(fact.start_byte, fact.end_byte)
13733                .expect("structured include fact range must be in prepared tree");
13734            assert_eq!(
13735                node.kind(),
13736                "preproc_include",
13737                "structured include fact range must identify its include node"
13738            );
13739            callable_preprocessor_context_is_visible_for_reference(
13740                node,
13741                prepared.source(),
13742                &reference,
13743            ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
13744        })
13745}
13746
13747fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
13748    let descendant = node;
13749    while let Some(parent) = node.parent() {
13750        if is_preprocessor_conditional(parent)
13751            && !is_file_covering_include_guard(parent, source)
13752            && preprocessor_conditional_contains_descendant(parent, descendant)
13753        {
13754            return true;
13755        }
13756        node = parent;
13757    }
13758    false
13759}
13760
13761/// The [`OwningPreprocessorConditionals`] of a macro event at `event`.
13762///
13763/// The descendant this walks up from is the one
13764/// [`VisibilityIndex::macro_event_condition_value`] starts from -- the
13765/// innermost node at the event's first byte, not the event node itself --
13766/// because containment compares that descendant's end against the recovered
13767/// conditional boundary, and the two nodes end in different places. An event
13768/// that [`has_preprocessor_conditional_ancestor`] rejects owns nothing: that
13769/// predicate is what has always decided whether an event is conditional at
13770/// all, and answering it first also skips the walk for the ordinary
13771/// unconditional event.
13772fn owning_preprocessor_conditionals(
13773    root: Node<'_>,
13774    event: Node<'_>,
13775    source: &str,
13776) -> OwningPreprocessorConditionals {
13777    if !has_preprocessor_conditional_ancestor(event, source) {
13778        return OwningPreprocessorConditionals::default();
13779    }
13780    let start = event.start_byte();
13781    let descendant = root
13782        .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
13783        .expect("a byte inside the parsed tree names a descendant");
13784    let mut owners = Vec::new();
13785    let mut current = descendant.parent();
13786    while let Some(conditional) = current {
13787        if is_preprocessor_conditional(conditional)
13788            && !is_file_covering_include_guard(conditional, source)
13789            && preprocessor_conditional_contains_descendant(conditional, descendant)
13790        {
13791            owners.push(conditional.start_byte());
13792        }
13793        current = conditional.parent();
13794    }
13795    owners.into_boxed_slice()
13796}
13797
13798fn is_preprocessor_conditional(node: Node<'_>) -> bool {
13799    matches!(
13800        node.kind(),
13801        "preproc_if"
13802            | "preproc_ifdef"
13803            | "preproc_ifndef"
13804            | "preproc_elif"
13805            | "preproc_elifdef"
13806            | "preproc_else"
13807    )
13808}
13809
13810fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
13811    node.parent()
13812        .filter(|parent| parent.kind() == "translation_unit")
13813        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
13814        && is_canonical_include_guard(node, source)
13815}
13816
13817fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
13818    if node.kind() != "preproc_ifdef"
13819        || node
13820            .child(0)
13821            .is_none_or(|directive| directive.kind() != "#ifndef")
13822        || node.child_by_field_name("alternative").is_some()
13823    {
13824        return false;
13825    }
13826    let Some(guard_name) = node.child_by_field_name("name") else {
13827        return false;
13828    };
13829    let mut cursor = node.walk();
13830    node.named_children(&mut cursor)
13831        .find(|child| *child != guard_name && child.kind() != "comment")
13832        .filter(|child| child.kind() == "preproc_def")
13833        .and_then(|definition| definition.child_by_field_name("name"))
13834        .is_some_and(|defined_name| {
13835            node_text(defined_name, source) == node_text(guard_name, source)
13836        })
13837}
13838
13839fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
13840    let mut guard = None;
13841    for child in named_children_iter(root) {
13842        if child.kind() == "comment" || is_pragma_once(child, source) {
13843            continue;
13844        }
13845        if guard.is_none() && is_canonical_include_guard(child, source) {
13846            guard = Some(child);
13847        } else {
13848            return None;
13849        }
13850    }
13851    guard
13852        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
13853        .map(|name| node_text(name, source).to_string())
13854}
13855
13856fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
13857    if (0..root.named_child_count())
13858        .filter_map(|index| root.named_child(index))
13859        .any(|child| is_pragma_once(child, source))
13860    {
13861        return MacroIncludeProtection::PragmaOnce;
13862    }
13863    top_level_canonical_include_guard_name(root, source)
13864        .map(MacroIncludeProtection::MacroGuard)
13865        .unwrap_or(MacroIncludeProtection::None)
13866}
13867
13868fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
13869    node.kind() == "preproc_call"
13870        && node
13871            .child_by_field_name("directive")
13872            .is_some_and(|directive| node_text(directive, source) == "#pragma")
13873        && node
13874            .child_by_field_name("argument")
13875            .is_some_and(|argument| node_text(argument, source).trim() == "once")
13876}
13877
13878fn parse_preproc_identifier(argument: &str) -> Option<String> {
13879    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
13880    let mut parser = Parser::new();
13881    parser
13882        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13883        .ok()?;
13884    let tree = parser.parse(&sentinel, None)?;
13885    if tree.root_node().has_error() {
13886        return None;
13887    }
13888    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
13889    let identifier = statement.named_child(0)?;
13890    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
13891        .then(|| node_text(identifier, &sentinel).to_string())
13892}
13893
13894pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
13895    match node.kind() {
13896        "identifier" | "field_identifier" => {
13897            let name = node_text(node, source).trim();
13898            (!name.is_empty()).then(|| name.to_string())
13899        }
13900        "abstract_array_declarator"
13901        | "abstract_function_declarator"
13902        | "abstract_parenthesized_declarator"
13903        | "abstract_pointer_declarator"
13904        | "abstract_reference_declarator" => None,
13905        "function_declarator" => node
13906            .child_by_field_name("declarator")
13907            .or_else(|| node.child_by_field_name("name"))
13908            .and_then(|child| extract_variable_name(child, source)),
13909        _ => node
13910            .child_by_field_name("declarator")
13911            .or_else(|| node.child_by_field_name("name"))
13912            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
13913            .and_then(|child| extract_variable_name(child, source)),
13914    }
13915}
13916
13917/// Whether `file` is proven to use plain-C source semantics.
13918///
13919/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
13920/// compilation dialect on their own, so only an exact `.c` source extension is
13921/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
13922/// identifiers.
13923///
13924/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
13925/// which extraction reads too (a `.c` file is extracted with C tag scope), so
13926/// the doctrine has exactly one definition.
13927pub fn is_c_source_file(file: &ProjectFile) -> bool {
13928    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
13929}
13930
13931/// Whether tree-sitter parsed the operand of C `sizeof(T)` as an expression
13932/// identifier even though `T` may denote a typedef.
13933///
13934/// The grammar cannot distinguish `sizeof(value)` from `sizeof(Type)` without
13935/// semantic information. Keep this helper structural and narrow; callers must
13936/// still prove a visible type and reject an active ordinary-namespace shadow.
13937pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
13938    if !is_c_source_file(file) || node.kind() != "identifier" {
13939        return false;
13940    }
13941    let mut operand = node;
13942    while let Some(parent) = operand.parent().filter(|parent| {
13943        parent.kind() == "parenthesized_expression"
13944            && parent.named_child_count() == 1
13945            && parent.named_child(0) == Some(operand)
13946    }) {
13947        operand = parent;
13948    }
13949    operand.parent().is_some_and(|parent| {
13950        parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
13951    })
13952}
13953
13954/// Return the type and member leaves of a C `offsetof` member designator.
13955///
13956/// `offsetof_expression` is a dedicated tree-sitter node, so its two operands
13957/// must be interpreted through their named fields.  In particular, do not
13958/// infer the aggregate from the enclosing lexical scope: an `offsetof` can
13959/// name a member of an unrelated aggregate, including a field promoted from
13960/// an anonymous union.  A missing or unsupported operand is deliberately
13961/// rejected so callers can keep the reference unresolved.
13962pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
13963    if node.kind() != "field_identifier" {
13964        return None;
13965    }
13966    let expression = node.parent().filter(|parent| {
13967        parent.kind() == "offsetof_expression" && parent.child_by_field_name("member") == Some(node)
13968    })?;
13969    if expression.has_error() {
13970        return None;
13971    }
13972    let closing = expression.child(expression.child_count().saturating_sub(1))?;
13973    if closing.kind() != ")" || closing.is_missing() {
13974        return None;
13975    }
13976    let type_descriptor = expression.child_by_field_name("type")?;
13977    if type_descriptor.kind() != "type_descriptor"
13978        || type_descriptor.is_missing()
13979        || type_descriptor.has_error()
13980    {
13981        return None;
13982    }
13983    let type_specifier = type_descriptor.child_by_field_name("type")?;
13984    if type_specifier.is_missing() || type_specifier.has_error() {
13985        return None;
13986    }
13987    let type_reference = match type_specifier.kind() {
13988        "class_specifier" | "struct_specifier" | "union_specifier" => {
13989            type_specifier.child_by_field_name("name")?
13990        }
13991        _ => type_specifier,
13992    };
13993    (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
13994}
13995
13996/// Whether `node` is the member leaf of an `offsetof_expression`, including a
13997/// malformed type operand.  Callers use this guard to prevent the ordinary
13998/// field-name heuristics from guessing an owner after structured resolution
13999/// has failed.
14000pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
14001    node.kind() == "field_identifier"
14002        && node.parent().is_some_and(|parent| {
14003            parent.kind() == "offsetof_expression"
14004                && parent.child_by_field_name("member") == Some(node)
14005        })
14006}
14007
14008/// Whether `node` is a template argument name that tree-sitter spelled with
14009/// type syntax.
14010///
14011/// The grammar cannot tell a type argument from a non-type (value) argument, so
14012/// it gives both the same shape:
14013/// `template_argument_list -> type_descriptor -> type_identifier`. In
14014/// `std::array<W, N>` the type `W` and the constant `N` parse identically, and
14015/// so do `std::span<const uint8_t, ED448_LEN>`'s length and a nested type
14016/// member used as a real type argument.
14017///
14018/// This helper reports only the syntactic position. A caller must still prove
14019/// which namespace explains the spelling: forward navigation asks the type
14020/// namespace first and reads the leaf as a value only when no type explains it,
14021/// and the inverse field scan admits the leaf only when no visible type does
14022/// (#2556).
14023pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
14024    if node.kind() != "type_identifier" {
14025        return false;
14026    }
14027    let Some(descriptor) = node
14028        .parent()
14029        .filter(|parent| parent.kind() == "type_descriptor")
14030    else {
14031        return false;
14032    };
14033    if descriptor.child_by_field_name("type") != Some(node) {
14034        return false;
14035    }
14036    let Some(arguments) = descriptor
14037        .parent()
14038        .filter(|parent| parent.kind() == "template_argument_list")
14039    else {
14040        return false;
14041    };
14042    arguments.parent().is_some_and(|owner| {
14043        matches!(
14044            owner.kind(),
14045            "template_type" | "template_function" | "template_method"
14046        ) && owner.child_by_field_name("arguments") == Some(arguments)
14047    })
14048}
14049
14050/// Whether a reference written in `file` reads C++ source with C semantics.
14051///
14052/// [`is_c_source_file`] answers the half a path settles on its own. The other
14053/// half is a header, which has no dialect of its own: it is read as C exactly
14054/// when every workspace translation unit that provably compiles it compiles it
14055/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
14056///
14057/// This is the gate for anything that is really about the compilation
14058/// language of the code being read -- which reading of an included header's
14059/// declarations is in scope, whether `this` is an ordinary identifier. It is
14060/// NOT the gate for a question that is genuinely about a `.c` file on disk;
14061/// those keep calling [`is_c_source_file`].
14062pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
14063    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
14064}
14065
14066pub fn is_declarator_node(node: Node<'_>) -> bool {
14067    matches!(
14068        node.kind(),
14069        "identifier"
14070            | "field_identifier"
14071            | "qualified_identifier"
14072            | "scoped_identifier"
14073            | "pointer_declarator"
14074            | "reference_declarator"
14075            | "array_declarator"
14076            | "parenthesized_declarator"
14077            | "function_declarator"
14078    )
14079}
14080
14081/// Returns the declarator represented by one direct child of a declaration.
14082///
14083/// Tree-sitter exposes the first declarator through the `declarator` field,
14084/// but subsequent comma-separated declarators are unfielded direct children:
14085/// `int first, second;` therefore has an `identifier` child for `first` and a
14086/// second, otherwise identical, `identifier` child for `second`. Consumers
14087/// must inspect both shapes or a later declarator can be mistaken for a use of
14088/// an unrelated indexed symbol.
14089pub fn declaration_declarator<'tree>(
14090    declaration: Node<'tree>,
14091    child: Node<'tree>,
14092) -> Option<Node<'tree>> {
14093    if !matches!(
14094        declaration.kind(),
14095        "declaration"
14096            | "field_declaration"
14097            | "parameter_declaration"
14098            | "optional_parameter_declaration"
14099            | "function_definition"
14100            | "type_definition"
14101            | "alias_declaration"
14102            | "template_instantiation"
14103    ) {
14104        return None;
14105    }
14106    if declaration
14107        .child_by_field_name("type")
14108        .is_some_and(|type_node| same_node(type_node, child))
14109    {
14110        return None;
14111    }
14112    if child.kind() == "init_declarator" {
14113        return child.child_by_field_name("declarator");
14114    }
14115    let field = field_name_in_parent(declaration, child);
14116    if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
14117        || (declaration.kind() == "type_definition"
14118            && child.kind() == "type_identifier"
14119            && matches!(field, Some("declarator") | None))
14120    {
14121        Some(child)
14122    } else {
14123        None
14124    }
14125}
14126
14127/// One run of a container's children whose parsed namespaces differ from
14128/// their lexical namespaces, with the complete lexical namespace path.
14129#[derive(Clone, Debug, PartialEq, Eq)]
14130pub struct RecoveredNamespaceRegion {
14131    /// Start byte of the first child in the run.
14132    pub start: usize,
14133    /// End byte of the last child in the run.
14134    pub end: usize,
14135    /// The complete enclosing namespace path of the run, outermost first.
14136    /// This can be empty when a parsed namespace extends past its real close.
14137    pub components: Vec<String>,
14138}
14139
14140/// The namespaces C++ parse recovery drops from a file's tree.
14141///
14142/// When tree-sitter cannot parse a construct inside a namespace body it closes
14143/// an inner scope with a MISSING brace, or skips an opening brace into an
14144/// ERROR node. Every real `}` after that then closes one scope too early: a
14145/// class body's `}` closes the namespace, the namespace's own `}` closes its
14146/// parent, and the outermost real closes land in a trailing ERROR node. The
14147/// declarations between a stolen close and the real one keep their byte
14148/// positions but lose their `namespace_definition` ancestors (Catch2's
14149/// `catch_matchers_templated.hpp`, issue #1537).
14150///
14151/// A file-global stack over real AST brace tokens restores both lost and
14152/// overextended namespaces. Missing braces do nothing; error-free subtrees are
14153/// balanced and can be skipped. For each child of a damaged container, compare
14154/// the namespaces still open on that stack with the parsed ancestor path. A
14155/// difference yields a region carrying the complete lexical path, including
14156/// an empty path when recovery swallowed file-scope declarations. Consecutive
14157/// children sharing that path form one [`RecoveredNamespaceRegion`]. The same
14158/// pass retains matching brace positions for declaration partitioning (#3087).
14159/// A file without parse errors needs neither correction nor a brace index.
14160#[derive(Clone, Debug, Default)]
14161pub struct OrphanedNamespaceScopeIndex {
14162    regions: Vec<RecoveredNamespaceRegion>,
14163    brace_closes: HashMap<usize, Range>,
14164}
14165
14166impl OrphanedNamespaceScopeIndex {
14167    pub fn build(root: Node<'_>, source: &str) -> Self {
14168        if !root.has_error() {
14169            return Self::default();
14170        }
14171        struct Frame<'tree> {
14172            node: Node<'tree>,
14173            children: Vec<Node<'tree>>,
14174            next: usize,
14175            parsed_scope: Vec<String>,
14176            run: Option<RecoveredNamespaceRegion>,
14177        }
14178        fn frame<'tree>(
14179            node: Node<'tree>,
14180            mut parsed_scope: Vec<String>,
14181            source: &str,
14182        ) -> Frame<'tree> {
14183            if node.kind() == "namespace_definition"
14184                && let Some(name) = node.child_by_field_name("name")
14185            {
14186                let mut components = Vec::new();
14187                if append_cpp_name_components(name, source, &mut components).is_some() {
14188                    parsed_scope.extend(components);
14189                }
14190            }
14191            let mut cursor = node.walk();
14192            Frame {
14193                node,
14194                children: node.children(&mut cursor).collect(),
14195                next: 0,
14196                parsed_scope,
14197                run: None,
14198            }
14199        }
14200        let mut regions = Vec::new();
14201        let mut brace_closes = HashMap::default();
14202        // The brace stack belongs to the file, not to a parser frame. A real
14203        // close inside a damaged child can close its parent's namespace. The
14204        // following children must see that removal immediately (#3087).
14205        let mut open = Vec::new();
14206        let mut lexical_scope = Vec::new();
14207        let mut frames = vec![frame(root, Vec::new(), source)];
14208        // A frame's node is the previous frame's direct child, so the frame
14209        // below answers what Node::parent would without re-descending from the
14210        // root; per-node parent or sibling climbs are quadratic over a wide
14211        // translation unit (#3141).
14212        while !frames.is_empty() {
14213            let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
14214            let current = frames.last_mut().expect("frames is non-empty");
14215            if current.next == current.children.len() {
14216                regions.extend(frames.pop().expect("the frame just borrowed").run);
14217                continue;
14218            }
14219            let child = current.children[current.next];
14220            current.next += 1;
14221            match child.kind() {
14222                "{" if !child.is_missing() => {
14223                    regions.extend(current.run.take());
14224                    let mut components = parent_node
14225                        .map(|parent| namespace_body_name_components(parent, current.node, source))
14226                        .unwrap_or_default();
14227                    if components.is_empty() {
14228                        components = recovered_namespace_open_components(
14229                            &current.children[..current.next - 1],
14230                            source,
14231                        );
14232                    }
14233                    open.push((child.start_byte(), lexical_scope.len()));
14234                    lexical_scope.extend(components);
14235                    continue;
14236                }
14237                "}" if !child.is_missing() => {
14238                    regions.extend(current.run.take());
14239                    if let Some((start, namespace_len)) = open.pop() {
14240                        lexical_scope.truncate(namespace_len);
14241                        brace_closes.insert(
14242                            start,
14243                            Range {
14244                                start_byte: child.start_byte(),
14245                                end_byte: child.end_byte(),
14246                                start_line: child.start_position().row + 1,
14247                                end_line: child.end_position().row + 1,
14248                            },
14249                        );
14250                    }
14251                    continue;
14252                }
14253                _ => {}
14254            }
14255            // The namespace head and body precede their opening brace; their
14256            // own parsed namespace is not yet on the lexical stack. Compare
14257            // the items inside the body, not this header bookkeeping.
14258            if current.node.kind() != "namespace_definition"
14259                && lexical_scope != current.parsed_scope
14260            {
14261                match &mut current.run {
14262                    Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14263                    run => {
14264                        regions.extend(run.take());
14265                        *run = Some(RecoveredNamespaceRegion {
14266                            start: child.start_byte(),
14267                            end: child.end_byte(),
14268                            components: lexical_scope.clone(),
14269                        });
14270                    }
14271                }
14272            } else {
14273                regions.extend(current.run.take());
14274            }
14275            // Error-free subtrees are balanced. Keeping them intact also
14276            // leaves their valid nested namespaces to the ordinary ancestry
14277            // walk in restore_enclosing_namespaces.
14278            if child.has_error() {
14279                let parsed_scope = current.parsed_scope.clone();
14280                frames.push(frame(child, parsed_scope, source));
14281            }
14282        }
14283        Self {
14284            regions,
14285            brace_closes,
14286        }
14287    }
14288
14289    /// The real AST brace matching an opening brace in a damaged subtree.
14290    /// Missing tokens and braces inside comments or literals do not participate.
14291    pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14292        self.brace_closes.get(&open).copied()
14293    }
14294
14295    pub fn is_empty(&self) -> bool {
14296        self.regions.is_empty()
14297    }
14298
14299    /// The bytes this index holds, for the analyzer cache's weight.
14300    pub fn approximate_size(&self) -> usize {
14301        self.regions.iter().fold(
14302            self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14303            |total, region| {
14304                total
14305                    .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14306                    .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14307            },
14308        )
14309    }
14310
14311    /// The innermost recovered region containing `byte`.
14312    pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14313        self.regions
14314            .iter()
14315            .filter(|region| region.start <= byte && byte < region.end)
14316            .min_by_key(|region| region.end - region.start)
14317    }
14318
14319    /// The enclosing namespaces of `node`, outermost first, restoring the ones
14320    /// parse recovery dropped from its ancestor chain. The one answer both
14321    /// lookup directions and declaration collection use (issue #1537).
14322    pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14323        let mut parsed = Vec::new();
14324        let mut current = node.parent();
14325        while let Some(parent) = current {
14326            if parent.kind() == "namespace_definition"
14327                && let Some(name) = parent.child_by_field_name("name")
14328            {
14329                let mut components = Vec::new();
14330                if append_cpp_name_components(name, source, &mut components).is_some() {
14331                    parsed.push((parent.start_byte(), components));
14332                }
14333            }
14334            current = parent.parent();
14335        }
14336        parsed.reverse();
14337        self.restore_enclosing_namespaces(parsed, node.start_byte())
14338    }
14339
14340    /// [`Self::enclosing_namespace_components`] for a caller that has already
14341    /// climbed the ancestor chain: `parsed` lists the node's named
14342    /// `namespace_definition` ancestors outermost first, each with its start
14343    /// byte. A region covering the node supplies every namespace outside it;
14344    /// only the parsed ancestors that start inside the region still apply.
14345    pub fn restore_enclosing_namespaces(
14346        &self,
14347        parsed: Vec<(usize, Vec<String>)>,
14348        node_start: usize,
14349    ) -> Vec<String> {
14350        let Some(region) = self.region_at(node_start) else {
14351            return parsed
14352                .into_iter()
14353                .flat_map(|(_, components)| components)
14354                .collect();
14355        };
14356        region
14357            .components
14358            .iter()
14359            .cloned()
14360            .chain(
14361                parsed
14362                    .into_iter()
14363                    .filter(|(start, _)| *start >= region.start)
14364                    .flat_map(|(_, components)| components),
14365            )
14366            .collect()
14367    }
14368}
14369
14370/// The name components of the namespace a stray `{` opens, or empty when `open`
14371/// does not follow a namespace head.
14372///
14373/// When a namespace body holds a construct tree-sitter cannot parse, recovery
14374/// can collapse the whole `namespace Name { ... }` into one `ERROR` instead of
14375/// a `namespace_definition`: the head survives as the node's own `namespace`
14376/// keyword, name and `{` tokens, in that order, and everything the namespace
14377/// declares becomes a flat sibling of them (Catch2's `catch_decomposer.hpp`,
14378/// issue #3084). Read the head from those siblings so the brace stack names
14379/// the scope the brace opens. An anonymous namespace has no representable
14380/// name and keeps an opaque scope.
14381///
14382/// `preceding` is the open brace's preceding siblings, nearest last. The
14383/// caller already holds the container's child list; `Node::prev_sibling`
14384/// would re-descend from the root for each step (#3141).
14385fn recovered_namespace_open_components(preceding: &[Node<'_>], source: &str) -> Vec<String> {
14386    let mut head = Vec::new();
14387    for &sibling in preceding.iter().rev() {
14388        if sibling.kind() != "comment" {
14389            head.push(sibling);
14390            if head.len() == 2 {
14391                break;
14392            }
14393        }
14394    }
14395    let [name, keyword] = head[..] else {
14396        return Vec::new();
14397    };
14398    if keyword.kind() != "namespace" {
14399        return Vec::new();
14400    }
14401    let mut components = Vec::new();
14402    if append_cpp_name_components(name, source, &mut components).is_none() {
14403        components.clear();
14404    }
14405    components
14406}
14407
14408/// The name components of the namespace whose body `body` is, or empty when
14409/// `body` is not the body of a named `namespace_definition` `parent`.
14410fn namespace_body_name_components(parent: Node<'_>, body: Node<'_>, source: &str) -> Vec<String> {
14411    let mut components = Vec::new();
14412    if body.kind() == "declaration_list"
14413        && parent.kind() == "namespace_definition"
14414        && parent.child_by_field_name("body") == Some(body)
14415        && let Some(name) = parent.child_by_field_name("name")
14416        && append_cpp_name_components(name, source, &mut components).is_none()
14417    {
14418        components.clear();
14419    }
14420    components
14421}
14422
14423#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14424pub enum RecoveredDeclaratorTypeContext {
14425    Declaration,
14426    FunctionDefinition,
14427    Parameter,
14428}
14429
14430/// Recognize a real type displaced into a qualified declarator by parser
14431/// recovery.
14432///
14433/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
14434/// type and `Result` were the scope of a qualified declarator with a missing
14435/// `::`. A template return such as `API Result<T> make()` uses a
14436/// `template_type` for the same recovered scope. The same recovery occurs for
14437/// macro-prefixed definitions, extern variables, and macro-decorated
14438/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
14439/// the macro). Keep this intentionally structural: the recovered scope must
14440/// have the grammar's missing separator, the qualified node must occupy the
14441/// declaration's declarator chain, a separate nonempty type must occupy the
14442/// normal type field, and the recovered name must unwrap to a real declarator
14443/// name.
14444pub fn recovered_macro_decorated_declarator_type(
14445    node: Node<'_>,
14446) -> Option<RecoveredDeclaratorTypeContext> {
14447    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
14448}
14449
14450/// Return the declaration/function `type` displaced by a macro-shaped
14451/// qualified declarator, together with the enclosing declaration context.
14452/// Callers use the macro scope only as structural admission evidence; the
14453/// returned node is the real type reference to resolve and record.
14454pub fn recovered_macro_decorated_type_node(
14455    node: Node<'_>,
14456) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14457    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
14458        return None;
14459    }
14460    let qualified = node.parent()?;
14461    if qualified.kind() != "qualified_identifier"
14462        || qualified.child_by_field_name("scope") != Some(node)
14463        || !(0..qualified.child_count())
14464            .filter_map(|index| qualified.child(index))
14465            .any(|child| child.kind() == "::" && child.is_missing())
14466    {
14467        return None;
14468    }
14469    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
14470        return None;
14471    }
14472
14473    let (declaration, context) = recovered_declarator_container(qualified)?;
14474    let type_node = declaration
14475        .child_by_field_name("type")
14476        .filter(|type_node| {
14477            *type_node != qualified
14478                && !type_node.is_missing()
14479                && type_node.start_byte() != type_node.end_byte()
14480        })?;
14481    Some((type_node, context))
14482}
14483
14484fn recovered_declarator_container(
14485    mut declarator: Node<'_>,
14486) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14487    loop {
14488        let parent = declarator.parent()?;
14489        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
14490            return Some((
14491                parent
14492                    .parent()
14493                    .filter(|declaration| declaration.kind() == "declaration")?,
14494                RecoveredDeclaratorTypeContext::Declaration,
14495            ));
14496        }
14497        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
14498            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
14499        }
14500        if parent.kind() == "function_definition"
14501            && has_field_child(parent, "declarator", declarator)
14502        {
14503            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
14504        }
14505        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
14506        // level down: the parameter's `type` field takes the macro token and
14507        // the real type `T` becomes the recovered scope of the declarator.
14508        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
14509        // candidate at all (#1830).
14510        if matches!(
14511            parent.kind(),
14512            "parameter_declaration" | "optional_parameter_declaration"
14513        ) && has_field_child(parent, "declarator", declarator)
14514        {
14515            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
14516        }
14517        if !matches!(
14518            parent.kind(),
14519            "array_declarator"
14520                | "function_declarator"
14521                | "parenthesized_declarator"
14522                | "pointer_declarator"
14523                | "pointer_type_declarator"
14524                | "reference_declarator"
14525        ) || !has_field_child(parent, "declarator", declarator)
14526        {
14527            return None;
14528        }
14529        declarator = parent;
14530    }
14531}
14532
14533fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
14534    let mut cursor = parent.walk();
14535    parent
14536        .children_by_field_name(field, &mut cursor)
14537        .any(|child| child == target)
14538}
14539
14540fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
14541    loop {
14542        if node.is_missing() || node.start_byte() == node.end_byte() {
14543            return false;
14544        }
14545        match node.kind() {
14546            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
14547                return true;
14548            }
14549            "array_declarator"
14550            | "function_declarator"
14551            | "parenthesized_declarator"
14552            | "pointer_declarator"
14553            | "pointer_type_declarator"
14554            | "reference_declarator" => {
14555                let Some(declarator) = node.child_by_field_name("declarator") else {
14556                    return false;
14557                };
14558                node = declarator;
14559            }
14560            _ => return false,
14561        }
14562    }
14563}
14564
14565/// Aggregate-owner proof for a structurally recognized designated initializer.
14566pub enum DesignatedInitializerOwner {
14567    Resolved(CodeUnit),
14568    Unresolved,
14569}
14570
14571enum InitializerOwnerStep {
14572    Field(String),
14573    AggregateWrapper,
14574}
14575
14576/// Recognize a designated-initializer field and, when possible, resolve its
14577/// aggregate owner.
14578///
14579/// Covers both the grammar's ordinary `field_designator` shape and the exact
14580/// recovery used for `.field = value` after a preprocessor-split array
14581/// initializer. Ordered multi-component designators follow each preceding
14582/// field's declared aggregate type. `None` means the node is not a designator
14583/// at all; an unresolved designator remains classified so callers cannot fall
14584/// through to unrelated global/member heuristics.
14585pub fn designated_initializer_owner(
14586    analyzer: &CppGraphSource<'_>,
14587    visibility: &VisibilityIndex<'_>,
14588    file: &ProjectFile,
14589    source: &str,
14590    node: Node<'_>,
14591) -> Option<DesignatedInitializerOwner> {
14592    if let Some(designator) = node
14593        .parent()
14594        .filter(|parent| parent.kind() == "field_designator")
14595    {
14596        let pair = designator.parent()?;
14597        if pair.kind() != "initializer_pair" {
14598            return None;
14599        }
14600        let mut cursor = pair.walk();
14601        let designators = pair
14602            .children_by_field_name("designator", &mut cursor)
14603            .collect::<Vec<_>>();
14604        let position = designators
14605            .iter()
14606            .position(|candidate| same_node(*candidate, designator))?;
14607        let initializer = pair.parent()?;
14608        if initializer.kind() != "initializer_list" {
14609            return None;
14610        }
14611        let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
14612        for prior in &designators[..position] {
14613            let field = prior
14614                .child_by_field_name("field")
14615                .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
14616            owner = owner.and_then(|owner| {
14617                initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
14618            });
14619        }
14620        return Some(classified_designated_owner(owner));
14621    }
14622
14623    let init_declarator = node.parent()?;
14624    if init_declarator.child_by_field_name("declarator") != Some(node)
14625        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
14626    {
14627        return None;
14628    }
14629    Some(classified_designated_owner(declaration_owner(
14630        analyzer,
14631        visibility,
14632        file,
14633        source,
14634        init_declarator.parent()?,
14635    )))
14636}
14637
14638fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
14639    owner.map_or(
14640        DesignatedInitializerOwner::Unresolved,
14641        DesignatedInitializerOwner::Resolved,
14642    )
14643}
14644
14645fn initializer_list_owner(
14646    analyzer: &CppGraphSource<'_>,
14647    visibility: &VisibilityIndex<'_>,
14648    file: &ProjectFile,
14649    source: &str,
14650    initializer: Node<'_>,
14651) -> Option<CodeUnit> {
14652    let mut current = initializer;
14653    let mut steps = Vec::new();
14654    loop {
14655        let parent = current.parent()?;
14656        match parent.kind() {
14657            "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
14658                let designator = parent.child_by_field_name("designator")?;
14659                let step = designator
14660                    .child_by_field_name("field")
14661                    .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
14662                    .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
14663                    .unwrap_or(InitializerOwnerStep::AggregateWrapper);
14664                steps.push(step);
14665                current = parent.parent()?;
14666            }
14667            "initializer_list" => {
14668                current = parent;
14669            }
14670            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
14671                let declaration = parent.parent()?;
14672                let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
14673                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14674            }
14675            "compound_literal_expression"
14676                if parent.child_by_field_name("value") == Some(current) =>
14677            {
14678                let type_node = parent.child_by_field_name("type")?;
14679                let owner =
14680                    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
14681                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14682            }
14683            "ERROR" => current = parent,
14684            _ => return None,
14685        }
14686    }
14687}
14688
14689fn apply_initializer_owner_steps(
14690    analyzer: &CppGraphSource<'_>,
14691    visibility: &VisibilityIndex<'_>,
14692    file: &ProjectFile,
14693    mut owner: CodeUnit,
14694    steps: Vec<InitializerOwnerStep>,
14695) -> Option<CodeUnit> {
14696    for step in steps.into_iter().rev() {
14697        if let InitializerOwnerStep::Field(field_name) = step {
14698            owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
14699        }
14700    }
14701    Some(owner)
14702}
14703
14704fn initializer_field_owner(
14705    analyzer: &CppGraphSource<'_>,
14706    visibility: &VisibilityIndex<'_>,
14707    file: &ProjectFile,
14708    owner: CodeUnit,
14709    field_name: &str,
14710) -> Option<CodeUnit> {
14711    let fields = visibility
14712        .visible_members_for_owner_name(file, &owner, field_name)
14713        .into_iter()
14714        .filter(|field| field.is_field())
14715        .collect::<Vec<_>>();
14716    let field = match fields.as_slice() {
14717        [field] => *field,
14718        _ => return None,
14719    };
14720    field_declared_binding(analyzer, visibility, file, field)?.unit
14721}
14722
14723fn declaration_owner(
14724    analyzer: &CppGraphSource<'_>,
14725    visibility: &VisibilityIndex<'_>,
14726    file: &ProjectFile,
14727    source: &str,
14728    declaration: Node<'_>,
14729) -> Option<CodeUnit> {
14730    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
14731        return None;
14732    }
14733    let type_node = declaration
14734        .child_by_field_name("type")
14735        .or_else(|| first_type_child(declaration))?;
14736    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
14737}
14738
14739fn resolve_designated_owner_type(
14740    analyzer: &CppGraphSource<'_>,
14741    visibility: &VisibilityIndex<'_>,
14742    file: &ProjectFile,
14743    source: &str,
14744    type_node: Node<'_>,
14745) -> Option<CodeUnit> {
14746    if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
14747        return Some(owner);
14748    }
14749    let type_name = normalize_type_text(node_text(type_node, source));
14750    visibility
14751        .resolve_type(file, &type_name)
14752        .filter(CodeUnit::is_class)
14753}
14754
14755pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
14756    let mut cursor = node.walk();
14757    node.named_children(&mut cursor).find(|child| {
14758        matches!(
14759            child.kind(),
14760            "type_identifier"
14761                | "primitive_type"
14762                | "qualified_identifier"
14763                | "scoped_type_identifier"
14764                | "struct_specifier"
14765                | "union_specifier"
14766                | "enum_specifier"
14767        )
14768    })
14769}
14770
14771pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
14772    visibility: &VisibilityIndex<'_>,
14773    file: &ProjectFile,
14774    source: &str,
14775    declarator: Node<'_>,
14776    type_text: Option<&str>,
14777    bindings: &LocalInferenceEngine<T>,
14778) -> bool {
14779    if !has_ancestor_kind(declarator, "compound_statement") {
14780        return false;
14781    }
14782    if declarator
14783        .child_by_field_name("declarator")
14784        .is_none_or(|declarator| declarator.kind() != "identifier")
14785    {
14786        return false;
14787    }
14788    if !type_text
14789        .and_then(|text| visibility.resolve_type(file, text))
14790        .is_some_and(|unit| unit.is_class())
14791    {
14792        return false;
14793    }
14794    declarator
14795        .child_by_field_name("parameters")
14796        .is_some_and(|parameters| {
14797            constructor_parameters_look_like_expressions(parameters, source, bindings)
14798        })
14799}
14800
14801fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
14802    parameters: Node<'_>,
14803    source: &str,
14804    bindings: &LocalInferenceEngine<T>,
14805) -> bool {
14806    let mut cursor = parameters.walk();
14807    parameters.named_children(&mut cursor).any(|parameter| {
14808        !matches!(
14809            parameter.kind(),
14810            "parameter_declaration" | "optional_parameter_declaration"
14811        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
14812    })
14813}
14814
14815fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
14816    parameter: Node<'_>,
14817    source: &str,
14818    bindings: &LocalInferenceEngine<T>,
14819) -> bool {
14820    let text = node_text(parameter, source).trim();
14821    if text
14822        .chars()
14823        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
14824        && bindings.is_shadowed(text)
14825    {
14826        return true;
14827    }
14828
14829    let Some(base) = parameter
14830        .child_by_field_name("type")
14831        .filter(|base| base.kind() == "type_identifier")
14832    else {
14833        return false;
14834    };
14835    let Some(subscript) = parameter
14836        .child_by_field_name("declarator")
14837        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
14838    else {
14839        return false;
14840    };
14841    subscript.child_by_field_name("size").is_some()
14842        && bindings.is_shadowed(node_text(base, source).trim())
14843}
14844
14845pub fn is_declaration_name(node: Node<'_>) -> bool {
14846    let Some(parent) = node.parent() else {
14847        return false;
14848    };
14849    if parent
14850        .child_by_field_name("name")
14851        .is_some_and(|name| same_node(name, node))
14852    {
14853        if matches!(
14854            parent.kind(),
14855            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14856        ) {
14857            return cpp_tag_specifier_declares_name(parent);
14858        }
14859        if matches!(
14860            parent.kind(),
14861            "namespace_definition"
14862                | "namespace_alias_definition"
14863                | "alias_declaration"
14864                | "enumerator"
14865        ) {
14866            return true;
14867        }
14868    }
14869
14870    let mut current = Some(parent);
14871    while let Some(ancestor) = current {
14872        let type_definition = ancestor.kind() == "type_definition";
14873        let mut child_cursor = ancestor.walk();
14874        if ancestor.named_children(&mut child_cursor).any(|child| {
14875            declaration_declarator(ancestor, child).is_some_and(|declarator| {
14876                declarator_name_path_contains(declarator, node, type_definition)
14877            })
14878        }) {
14879            return true;
14880        }
14881        if matches!(
14882            ancestor.kind(),
14883            "declaration"
14884                | "field_declaration"
14885                | "parameter_declaration"
14886                | "optional_parameter_declaration"
14887                | "function_definition"
14888                | "type_definition"
14889                | "alias_declaration"
14890                | "template_instantiation"
14891                | "class_specifier"
14892                | "struct_specifier"
14893                | "union_specifier"
14894                | "enum_specifier"
14895        ) {
14896            return false;
14897        }
14898        current = ancestor.parent();
14899    }
14900    false
14901}
14902
14903/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
14904/// declaration's declarator inside a malformed class body.
14905///
14906/// An export macro between `class` and the class name can make the containing
14907/// body parse as a function body. A source declaration such as
14908/// `friend class internal::Friend;` then retains this exact structure:
14909/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
14910/// The declarator is a type reference despite its field role.
14911pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
14912    if !matches!(
14913        node.kind(),
14914        "qualified_identifier" | "scoped_type_identifier"
14915    ) {
14916        return false;
14917    }
14918    let Some(declaration) = node
14919        .parent()
14920        .filter(|parent| parent.kind() == "declaration")
14921    else {
14922        return false;
14923    };
14924    if declaration.child_by_field_name("declarator") != Some(node)
14925        || !declaration
14926            .child_by_field_name("type")
14927            .is_some_and(|friend| {
14928                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
14929            })
14930    {
14931        return false;
14932    }
14933    let mut cursor = declaration.walk();
14934    let mut errors = declaration
14935        .named_children(&mut cursor)
14936        .filter(|child| child.kind() == "ERROR");
14937    let Some(error) = errors.next() else {
14938        return false;
14939    };
14940    errors.next().is_none()
14941        && error.named_child_count() == 1
14942        && error.named_child(0).is_some_and(|class| {
14943            class.kind() == "identifier" && node_text(class, source) == "class"
14944        })
14945}
14946
14947pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
14948    if !matches!(node.kind(), "identifier" | "field_identifier") {
14949        return false;
14950    }
14951    if let Some(parent) = node.parent() {
14952        if parent.kind() == "call_expression"
14953            && parent.child_by_field_name("function") == Some(node)
14954        {
14955            return false;
14956        }
14957        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
14958            && parent.child_by_field_name("label") == Some(node)
14959        {
14960            return false;
14961        }
14962    }
14963    if is_declaration_name(node) {
14964        return false;
14965    }
14966    let mut current = node.parent();
14967    while let Some(ancestor) = current {
14968        match ancestor.kind() {
14969            "preproc_ifdef" | "preproc_ifndef" => {
14970                if ancestor
14971                    .child_by_field_name("name")
14972                    .is_some_and(|name| node_range_contains(name, node))
14973                {
14974                    return false;
14975                }
14976            }
14977            "preproc_if" | "preproc_elif" => {
14978                if ancestor
14979                    .child_by_field_name("condition")
14980                    .is_some_and(|condition| node_range_contains(condition, node))
14981                {
14982                    return false;
14983                }
14984            }
14985            "preproc_else" => {}
14986            kind if kind.starts_with("preproc_") => return false,
14987            _ => {}
14988        }
14989        if matches!(
14990            ancestor.kind(),
14991            "translation_unit" | "function_definition" | "compound_statement"
14992        ) {
14993            break;
14994        }
14995        current = ancestor.parent();
14996    }
14997    true
14998}
14999
15000fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
15001    outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
15002}
15003
15004fn recovered_c_reference_node(
15005    visibility: &VisibilityIndex<'_>,
15006    file: &ProjectFile,
15007    node: Node<'_>,
15008    source: &str,
15009) -> bool {
15010    if node.start_byte() >= node.end_byte()
15011        || node.is_error()
15012        || node.is_missing()
15013        || !matches!(
15014            node.kind(),
15015            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
15016        )
15017        || recovered_c_macro_binding_role(node)
15018        || recovered_c_label_role(node)
15019    {
15020        return false;
15021    }
15022    // A declaration name can be an identifier child of a recovered ERROR
15023    // (for example `EFI_STATUS Encode()` in C files parsed as C++). Do not
15024    // let a same-named macro turn that binder into a reference. An
15025    // assignment whose C callee follows the recovered `explicit` token is
15026    // the one expression-shaped exception: the generic declarator walk sees
15027    // its function_declarator as a declaration path, but the ERROR sibling
15028    // proves it is a call.
15029    let name = node_text(node, source);
15030    let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
15031    let recovered_macro_call = recovered_c_function_declarator_invocation(node)
15032        && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
15033    let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
15034    if is_declaration_name(node)
15035        && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
15036        && !recovered_function_call
15037        && !recovered_macro_call
15038        && !recovered_parenthesized_reference
15039    {
15040        return false;
15041    }
15042
15043    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
15044        return true;
15045    }
15046    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
15047        return true;
15048    }
15049    if recovered_parenthesized_reference {
15050        return true;
15051    }
15052    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
15053        if recovered_function_call {
15054            return true;
15055        }
15056        return visibility
15057            .visible_identifier_candidates(file, name)
15058            .any(|candidate| {
15059                candidate.is_class() || candidate.is_module() || is_type_alias(candidate)
15060            });
15061    }
15062    let visible = visibility
15063        .visible_identifier_candidates(file, name)
15064        .next()
15065        .is_some();
15066    visible
15067        && (recovered_c_reference_anchor(node)
15068            || recovered_c_error_expression_leaf(node)
15069            || recovered_function_call)
15070}
15071
15072fn push_recovered_c_range(
15073    ranges: &mut Vec<Range>,
15074    seen: &mut HashSet<(usize, usize)>,
15075    start_byte: usize,
15076    end_byte: usize,
15077    node: Node<'_>,
15078    limit: usize,
15079) -> bool {
15080    if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
15081        return true;
15082    }
15083    if ranges.len() >= limit {
15084        return false;
15085    }
15086    ranges.push(Range {
15087        start_byte,
15088        end_byte,
15089        start_line: node.start_position().row,
15090        end_line: node.end_position().row,
15091    });
15092    true
15093}
15094
15095/// A reference leaf can sit directly beneath an ERROR while its ERROR parent
15096/// is still attached to a real expression (most often a recovered macro call
15097/// argument). The expression parent is the structured proof; an unindexed
15098/// identifier beneath a bare recovery envelope has no such proof.
15099fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
15100    let mut current = node.parent();
15101    while let Some(parent) = current {
15102        if parent.is_error() {
15103            let Some(anchor) = parent.parent() else {
15104                return false;
15105            };
15106            return anchor.kind().ends_with("_expression")
15107                || matches!(
15108                    anchor.kind(),
15109                    "argument_list"
15110                        | "return_statement"
15111                        | "expression_statement"
15112                        | "case_statement"
15113                        | "initializer_list"
15114                        | "field_designator"
15115                        | "enumerator"
15116                );
15117        }
15118        if matches!(
15119            parent.kind(),
15120            "translation_unit" | "function_definition" | "compound_statement"
15121        ) {
15122            return false;
15123        }
15124        current = parent.parent();
15125    }
15126    false
15127}
15128
15129/// C recovery may represent a call as `identifier > function_declarator >
15130/// function_declarator > ERROR > compound_statement`. This shape is only a
15131/// call when the malformed declarator is attached to a real function body and
15132/// the name is an indexed visible callable. A declaration's
15133/// `ERROR > function_declarator > declaration` shape deliberately fails this
15134/// test.
15135fn recovered_c_function_call(
15136    visibility: &VisibilityIndex<'_>,
15137    file: &ProjectFile,
15138    node: Node<'_>,
15139    name: &str,
15140) -> bool {
15141    if !matches!(
15142        node.kind(),
15143        "identifier" | "field_identifier" | "type_identifier"
15144    ) {
15145        return false;
15146    }
15147    // C++ keywords used as C arguments can leave only the call prefix under
15148    // ERROR, with the remaining arguments in a following expression statement.
15149    // Require a statement boundary in a real block, an opening parenthesis,
15150    // and a recovered argument. A bare ERROR identifier supplies no call role.
15151    let error_call_prefix = node.parent().is_some_and(|error| {
15152        error.is_error()
15153            && error
15154                .parent()
15155                .is_some_and(|parent| parent.kind() == "compound_statement")
15156    }) && node
15157        .prev_sibling()
15158        .is_none_or(|previous| previous.kind() == ";")
15159        && node.next_sibling().is_some_and(|open| {
15160            open.kind() == "("
15161                && open.next_named_sibling().is_some_and(|argument| {
15162                    argument.kind() == "parameter_declaration" && !argument.has_error()
15163                })
15164        });
15165    (error_call_prefix || recovered_c_function_declarator_invocation(node))
15166        && visibility
15167            .visible_identifier_candidates(file, name)
15168            .any(CodeUnit::is_function)
15169}
15170
15171/// Return whether a C identifier belongs to a call-shaped declarator that the
15172/// C++ grammar put under an `ERROR` node.
15173///
15174/// The malformed call can be direct (`f(arg)`) or nested in a parameter
15175/// declaration when one of its arguments looks like a type (`f(TYPE, value)`).
15176/// In both cases the CST retains the function-declarator and its enclosing
15177/// recovery envelope. We walk only those declarator/parameter nodes and stop
15178/// at a real expression-bearing boundary; declarations therefore cannot pass
15179/// this predicate merely because they have a parameter list.
15180fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
15181    let mut function_declarator = if node.parent().is_some_and(|parent| {
15182        parent.kind() == "function_declarator"
15183            && parent.child_by_field_name("declarator") == Some(node)
15184    }) {
15185        node.parent().expect("checked function declarator parent")
15186    } else {
15187        let Some(parameter) = node.parent().filter(|parent| {
15188            parent.kind() == "parameter_declaration"
15189                && parent.child_by_field_name("type") == Some(node)
15190        }) else {
15191            return false;
15192        };
15193        if !parameter
15194            .child_by_field_name("declarator")
15195            .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15196        {
15197            return false;
15198        }
15199        let Some(parameters) = parameter
15200            .parent()
15201            .filter(|parent| parent.kind() == "parameter_list")
15202        else {
15203            return false;
15204        };
15205        let Some(function_declarator) = parameters
15206            .parent()
15207            .filter(|parent| parent.kind() == "function_declarator")
15208        else {
15209            return false;
15210        };
15211        function_declarator
15212    };
15213
15214    // Recovery may absorb the next statement's parenthesized cast into a
15215    // second function declarator. Follow only the original declarator chain.
15216    while let Some(parent) = function_declarator.parent().filter(|parent| {
15217        parent.kind() == "function_declarator"
15218            && parent.child_by_field_name("declarator") == Some(function_declarator)
15219    }) {
15220        function_declarator = parent;
15221    }
15222    let Some(mut current) = function_declarator
15223        .parent()
15224        .filter(|parent| parent.is_error())
15225    else {
15226        return false;
15227    };
15228    loop {
15229        let Some(parent) = current.parent() else {
15230            return false;
15231        };
15232        if matches!(
15233            parent.kind(),
15234            "translation_unit"
15235                | "compound_statement"
15236                | "preproc_if"
15237                | "preproc_ifdef"
15238                | "preproc_ifndef"
15239                | "preproc_else"
15240                | "preproc_elif"
15241        ) {
15242            return true;
15243        }
15244        if parent.kind() == "function_definition"
15245            && parent.child_by_field_name("declarator") == Some(current)
15246            && parent.named_child(0) == Some(current)
15247            && parent.child_by_field_name("body").is_some()
15248        {
15249            return true;
15250        }
15251        if parent.is_error()
15252            || matches!(
15253                parent.kind(),
15254                "parameter_declaration"
15255                    | "parameter_list"
15256                    | "function_declarator"
15257                    | "abstract_function_declarator"
15258                    | "parenthesized_declarator"
15259            )
15260        {
15261            current = parent;
15262            continue;
15263        }
15264        return false;
15265    }
15266}
15267
15268/// C permits an identifier named `typename`. The C++ grammar can recover an
15269/// assignment using that identifier as a declaration whose declarator is a
15270/// parenthesized argument list, for example `typename = f(ctx, value)`. Only
15271/// the argument retained beneath the nested `ERROR` is a reference; sibling
15272/// declarator identifiers remain binders/grammar artifacts.
15273fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
15274    let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
15275        return false;
15276    };
15277    if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
15278        return false;
15279    }
15280    let Some(declarator) = error
15281        .parent()
15282        .filter(|parent| parent.kind() == "parenthesized_declarator")
15283    else {
15284        return false;
15285    };
15286    let Some(declaration) = declarator
15287        .parent()
15288        .filter(|parent| parent.kind() == "declaration")
15289    else {
15290        return false;
15291    };
15292    if declaration.child_by_field_name("declarator") != Some(declarator) {
15293        return false;
15294    }
15295    let Some(type_node) = declaration.child_by_field_name("type") else {
15296        return false;
15297    };
15298    type_node.kind() == "dependent_type"
15299        && type_node
15300            .child(0)
15301            .is_some_and(|keyword| keyword.kind() == "typename")
15302}
15303
15304fn recovered_c_explicit_assignment_callee(
15305    visibility: &VisibilityIndex<'_>,
15306    file: &ProjectFile,
15307    node: Node<'_>,
15308    name: &str,
15309) -> bool {
15310    let mut current = node;
15311    let error = loop {
15312        let Some(parent) = current.parent() else {
15313            return false;
15314        };
15315        if parent.is_error() {
15316            break parent;
15317        }
15318        current = parent;
15319    };
15320    let mut cursor = error.walk();
15321    let explicit_recovery_precedes_callee = error
15322        .named_children(&mut cursor)
15323        .take_while(|child| child.start_byte() < node.start_byte())
15324        .any(|child| child.kind() == "explicit_function_specifier");
15325    if !explicit_recovery_precedes_callee {
15326        return false;
15327    }
15328    visibility
15329        .visible_identifier_candidates(file, name)
15330        .any(CodeUnit::is_function)
15331}
15332
15333fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
15334    while let Some(parent) = node.parent() {
15335        if matches!(
15336            parent.kind(),
15337            "preproc_def" | "preproc_function_def" | "preproc_params"
15338        ) {
15339            return true;
15340        }
15341        if parent.is_error()
15342            || matches!(
15343                parent.kind(),
15344                "translation_unit" | "function_definition" | "compound_statement"
15345            )
15346        {
15347            return false;
15348        }
15349        node = parent;
15350    }
15351    false
15352}
15353
15354fn recovered_c_label_role(node: Node<'_>) -> bool {
15355    node.parent().is_some_and(|parent| {
15356        matches!(parent.kind(), "labeled_statement" | "goto_statement")
15357            && parent.child_by_field_name("label") == Some(node)
15358    })
15359}
15360
15361fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
15362    while let Some(parent) = node.parent() {
15363        if parent.is_error() {
15364            return false;
15365        }
15366        // A C macro call recovered as a function declarator can parse an
15367        // assignment-shaped argument as an optional parameter. Its
15368        // `default_value` field remains an expression role even though the
15369        // surrounding call shape is beneath ERROR.
15370        if parent.kind() == "optional_parameter_declaration"
15371            && parent
15372                .child_by_field_name("default_value")
15373                .is_some_and(|value| node_range_contains(value, node))
15374        {
15375            return true;
15376        }
15377        if parent.kind().ends_with("_expression")
15378            || matches!(
15379                parent.kind(),
15380                "argument_list"
15381                    | "return_statement"
15382                    | "expression_statement"
15383                    | "case_statement"
15384                    | "initializer_list"
15385                    | "init_declarator"
15386                    | "array_declarator"
15387                    | "field_designator"
15388                    | "enumerator"
15389            )
15390        {
15391            return true;
15392        }
15393        if matches!(
15394            parent.kind(),
15395            "translation_unit"
15396                | "function_definition"
15397                | "compound_statement"
15398                | "declaration"
15399                | "field_declaration"
15400                | "parameter_declaration"
15401        ) {
15402            return false;
15403        }
15404        node = parent;
15405    }
15406    false
15407}
15408
15409/// Whether a parameter declaration belongs to the callable scope whose body can
15410/// contain references to it.
15411///
15412/// Error recovery can wrap a macro-decorated class body in a synthetic outer
15413/// `function_definition`. Merely finding any callable ancestor would then leak
15414/// parameters from member prototypes into later member bodies. Require the
15415/// parameter to be inside that definition's own declarator instead.
15416pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
15417    let mut current = parameter.parent();
15418    while let Some(ancestor) = current {
15419        if ancestor.kind() == "lambda_expression" {
15420            return ancestor
15421                .child_by_field_name("declarator")
15422                .is_some_and(|declarator| {
15423                    declarator.start_byte() <= parameter.start_byte()
15424                        && parameter.end_byte() <= declarator.end_byte()
15425                });
15426        }
15427        if ancestor.kind() == "function_definition" {
15428            return ancestor
15429                .child_by_field_name("declarator")
15430                .is_some_and(|declarator| {
15431                    declarator.start_byte() <= parameter.start_byte()
15432                        && parameter.end_byte() <= declarator.end_byte()
15433                });
15434        }
15435        current = ancestor.parent();
15436    }
15437    false
15438}
15439
15440pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
15441    let mut current = node.parent();
15442    while let Some(ancestor) = current {
15443        if matches!(
15444            ancestor.kind(),
15445            "parameter_declaration" | "optional_parameter_declaration"
15446        ) {
15447            return ancestor
15448                .child_by_field_name("type")
15449                .is_some_and(|type_node| {
15450                    type_node.start_byte() <= node.start_byte()
15451                        && node.end_byte() <= type_node.end_byte()
15452                });
15453        }
15454        if matches!(
15455            ancestor.kind(),
15456            "function_definition" | "lambda_expression" | "compound_statement"
15457        ) {
15458            return false;
15459        }
15460        current = ancestor.parent();
15461    }
15462    false
15463}
15464
15465fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
15466    if specifier.child_by_field_name("body").is_some() {
15467        return true;
15468    }
15469    let mut current = specifier.parent();
15470    while let Some(ancestor) = current {
15471        match ancestor.kind() {
15472            "type_descriptor"
15473            | "parameter_declaration"
15474            | "optional_parameter_declaration"
15475            | "template_argument_list"
15476            | "cast_expression" => return false,
15477            "declaration" | "field_declaration" => {
15478                let mut cursor = ancestor.walk();
15479                return ancestor
15480                    .children_by_field_name("declarator", &mut cursor)
15481                    .next()
15482                    .is_none();
15483            }
15484            "translation_unit" => return true,
15485            _ => current = ancestor.parent(),
15486        }
15487    }
15488    false
15489}
15490
15491pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
15492    match node.kind() {
15493        "identifier"
15494        | "field_identifier"
15495        | "qualified_identifier"
15496        | "scoped_identifier"
15497        | "operator_name"
15498        | "destructor_name"
15499        | "literal_operator_name" => Some(node),
15500        "reference_declarator" | "parenthesized_declarator" => {
15501            node.named_child(0).and_then(declarator_name_node)
15502        }
15503        _ => node
15504            .child_by_field_name("declarator")
15505            .or_else(|| node.child_by_field_name("name"))
15506            .or_else(|| node.child_by_field_name("field"))
15507            .and_then(declarator_name_node),
15508    }
15509}
15510
15511fn declarator_name_path_contains(
15512    declarator: Node<'_>,
15513    candidate: Node<'_>,
15514    allow_type_identifier: bool,
15515) -> bool {
15516    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
15517        return false;
15518    };
15519    let mut current = Some(declarator);
15520    while let Some(node) = current {
15521        if same_node(node, candidate) {
15522            return true;
15523        }
15524        if same_node(node, name) {
15525            return false;
15526        }
15527        current = node
15528            .child_by_field_name("declarator")
15529            .or_else(|| node.child_by_field_name("name"))
15530            .or_else(|| node.child_by_field_name("field"));
15531    }
15532    false
15533}
15534
15535fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
15536    match node.kind() {
15537        "identifier"
15538        | "field_identifier"
15539        | "operator_name"
15540        | "destructor_name"
15541        | "literal_operator_name" => Some(node),
15542        "type_identifier" if allow_type_identifier => Some(node),
15543        _ => node
15544            .child_by_field_name("declarator")
15545            .or_else(|| node.child_by_field_name("name"))
15546            .or_else(|| node.child_by_field_name("field"))
15547            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
15548    }
15549}
15550
15551/// True when `node` is a component of a larger structured type node whose outer
15552/// range is the single reference surfaced to callers.
15553pub fn is_nested_type_node(node: Node<'_>) -> bool {
15554    node.parent().is_some_and(|parent| {
15555        matches!(
15556            parent.kind(),
15557            "qualified_identifier" | "scoped_type_identifier" | "template_type"
15558        )
15559    })
15560}
15561
15562pub struct OutOfLineMemberDefinitionOwners<'tree> {
15563    pub owners: Vec<(Node<'tree>, CodeUnit)>,
15564    innermost: Option<(Node<'tree>, CodeUnit)>,
15565}
15566
15567impl OutOfLineMemberDefinitionOwners<'_> {
15568    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
15569        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
15570    }
15571}
15572
15573pub struct QualifiedOwnerComponents<'tree> {
15574    pub nodes: Vec<Node<'tree>>,
15575    pub names: Vec<String>,
15576    pub global: bool,
15577}
15578
15579/// True when each structured qualifier on the callable-name path has a real
15580/// `::` token. A macro-prefixed return type can make tree-sitter insert a
15581/// zero-width missing separator and parse `TYPE Result<T> method()` as the
15582/// false qualified declarator `Result<T>::method`.
15583pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
15584    let mut stack = vec![node];
15585    let mut found_separator = false;
15586    while let Some(current) = stack.pop() {
15587        if !matches!(
15588            current.kind(),
15589            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15590        ) {
15591            continue;
15592        }
15593        let mut current_has_separator = false;
15594        for child in children_iter(current) {
15595            if child.kind() == "::" {
15596                if child.is_missing() {
15597                    return false;
15598                }
15599                current_has_separator = true;
15600                found_separator = true;
15601            }
15602        }
15603        if !current_has_separator {
15604            return false;
15605        }
15606        for field in ["scope", "name"] {
15607            if let Some(child) = current.child_by_field_name(field)
15608                && matches!(
15609                    child.kind(),
15610                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15611                )
15612            {
15613                stack.push(child);
15614            }
15615        }
15616    }
15617    found_separator
15618}
15619
15620pub fn qualified_owner_components<'tree>(
15621    node: Node<'tree>,
15622    source: &str,
15623) -> Option<QualifiedOwnerComponents<'tree>> {
15624    if !qualified_name_has_concrete_scope_separators(node) {
15625        return None;
15626    }
15627    let mut nodes = cpp_name_component_nodes(node)?;
15628    nodes.pop()?;
15629    if nodes.is_empty() {
15630        return None;
15631    }
15632    let names = nodes
15633        .iter()
15634        .map(|component| node_text(*component, source).to_string())
15635        .collect();
15636    Some(QualifiedOwnerComponents {
15637        nodes,
15638        names,
15639        global: is_globally_qualified_cpp_name(node),
15640    })
15641}
15642
15643pub fn out_of_line_member_definition_owner<'tree>(
15644    analyzer: &CppGraphSource<'_>,
15645    visibility: &VisibilityIndex<'_>,
15646    file: &ProjectFile,
15647    source: &str,
15648    node: Node<'tree>,
15649) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
15650    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
15651        || !has_ancestor_kind(node, "function_definition")
15652        || !is_function_declarator_name_root(node)
15653    {
15654        return None;
15655    }
15656    let qualified = qualified_owner_components(node, source)?;
15657    let lexical_scope = enclosing_namespace_components(node, source)?;
15658    let mut owners = Vec::new();
15659    let mut innermost = None;
15660
15661    for component_count in 1..=qualified.names.len() {
15662        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
15663            .resolve_type_components_lexically(
15664                analyzer,
15665                file,
15666                &qualified.names[..component_count],
15667                qualified.global,
15668                &lexical_scope,
15669            )
15670            && !owners
15671                .iter()
15672                .any(|(_, existing)| same_visible_symbol(existing, &unit))
15673        {
15674            if component_count == qualified.names.len() {
15675                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
15676            }
15677            owners.push((qualified.nodes[component_count - 1], unit));
15678        }
15679    }
15680
15681    // The C++ analyzer has already reconciled an indexed out-of-line callable
15682    // against the include-visible class table. Consult that canonical owner
15683    // chain only when ordinary lexical lookup could not recover the innermost
15684    // owner.  A one-segment qualifier is safe here only when the enclosing
15685    // indexed callable has an authoritative class owner and the parser's
15686    // namespace path is a (possibly sparse) subsequence of that owner path.
15687    // The latter is what lets macro-wrapped namespace sentinels recover a
15688    // missing `time_internal`/`cord_internal` component without guessing an
15689    // unrelated short name.
15690    if innermost.is_none() {
15691        let indexed_owner_components = visibility
15692            .indexed_enclosing_owner_scope(analyzer, file, node)
15693            .or_else(|| {
15694                // Retain the legacy rendered-name fallback for the existing
15695                // multi-segment path when an enclosing owner chain is not
15696                // available (for example, cache-loaded units without parent
15697                // links).  One-segment recovery must stay canonical-only.
15698                if qualified.names.len() <= 1 {
15699                    return None;
15700                }
15701                let range = Range {
15702                    start_byte: node.start_byte(),
15703                    end_byte: node.end_byte(),
15704                    start_line: node.start_position().row,
15705                    end_line: node.end_position().row,
15706                };
15707                let start = analyzer.enclosing_code_unit(file, &range)?;
15708                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
15709                    brokk_bifrost_core::analyzer::Language::Cpp,
15710                    &cpp_name_for(&start),
15711                );
15712                components.pop();
15713                Some(components)
15714            });
15715        if let Some(indexed_owner_components) = indexed_owner_components
15716            && indexed_owner_components.len() > qualified.names.len()
15717            && indexed_owner_components.ends_with(&qualified.names)
15718            && indexed_namespace_path_is_recoverable(
15719                &lexical_scope,
15720                &indexed_owner_components,
15721                qualified.names.len(),
15722            )
15723            // A globally-qualified one-segment owner is an explicit request
15724            // for the top-level binding; do not reinterpret it as a missing
15725            // namespace component.  Existing multi-segment global lookups
15726            // retain their historical indexed recovery.
15727            && (qualified.names.len() > 1 || !qualified.global)
15728        {
15729            let namespace_count = indexed_owner_components.len() - qualified.names.len();
15730            for component_count in 1..=qualified.names.len() {
15731                let expected = &indexed_owner_components[..namespace_count + component_count];
15732                let owner_node = qualified.nodes[component_count - 1];
15733                for owner in visibility
15734                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
15735                    .filter(|candidate| candidate.is_class())
15736                    .filter(|candidate| {
15737                        canonical_cpp_scope_components(candidate) == expected
15738                            && visibility.external_type_candidate_visible_in_context(
15739                                analyzer, file, candidate, node,
15740                            )
15741                    })
15742                {
15743                    if component_count == qualified.names.len() && innermost.is_none() {
15744                        innermost = Some((owner_node, owner.clone()));
15745                    }
15746                    if !owners
15747                        .iter()
15748                        .any(|(_, existing)| same_symbol(existing, owner))
15749                    {
15750                        owners.push((owner_node, owner.clone()));
15751                    }
15752                }
15753            }
15754        }
15755    }
15756    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
15757}
15758
15759fn is_function_declarator_name_root(node: Node<'_>) -> bool {
15760    let mut current = node;
15761    while let Some(parent) = current.parent() {
15762        if parent.kind() == "function_declarator" {
15763            return parent.child_by_field_name("declarator") == Some(current);
15764        }
15765        if matches!(
15766            parent.kind(),
15767            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
15768        ) && parent.child_by_field_name("declarator") == Some(current)
15769        {
15770            current = parent;
15771            continue;
15772        }
15773        return false;
15774    }
15775    false
15776}
15777
15778pub fn append_cpp_name_components(
15779    node: Node<'_>,
15780    source: &str,
15781    out: &mut Vec<String>,
15782) -> Option<()> {
15783    out.extend(
15784        cpp_name_component_nodes(node)?
15785            .into_iter()
15786            .map(|component| node_text(component, source).to_string()),
15787    );
15788    Some(())
15789}
15790
15791pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
15792    let mut components = Vec::new();
15793    append_cpp_name_components(node, source, &mut components)?;
15794    Some(components)
15795}
15796
15797/// Resolve a structured type spelling from an object-like macro replacement
15798/// when definition-site source order has no answer.
15799///
15800/// Macro replacement tokens are looked up where the macro is expanded, so a
15801/// type declared later in the defining header can still be their destination.
15802/// Without expanding every invocation, accept only one include-visible logical
15803/// class or alias whose structured path ends in the replacement components.
15804/// An ordinary lexical answer always takes precedence at the call site.
15805pub fn unique_macro_replacement_type_candidate(
15806    analyzer: &CppGraphSource<'_>,
15807    visibility: &VisibilityIndex<'_>,
15808    file: &ProjectFile,
15809    components: &[String],
15810) -> Option<CodeUnit> {
15811    let terminal = components.last()?;
15812    let mut candidates = Vec::new();
15813    for candidate in visibility
15814        .visible_identifier_candidates(file, terminal)
15815        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
15816        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
15817    {
15818        if !candidates
15819            .iter()
15820            .any(|existing| same_logical_symbol(existing, candidate))
15821        {
15822            candidates.push(candidate.clone());
15823        }
15824    }
15825    (candidates.len() == 1).then(|| candidates.remove(0))
15826}
15827
15828/// The base scopes named by member using-declarations for `member` in one
15829/// class source range.
15830///
15831/// The grammar supplies the qualified identifier and each component. Keep
15832/// this interpretation shared between forward overload lookup and inverse
15833/// owner routing rather than reparsing a rendered `Base::member` string at
15834/// either call site.
15835pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
15836    let mut parser = Parser::new();
15837    if parser
15838        .set_language(&tree_sitter_cpp::LANGUAGE.into())
15839        .is_err()
15840    {
15841        return Vec::new();
15842    }
15843    let Some(tree) = parser.parse(source, None) else {
15844        return Vec::new();
15845    };
15846    let mut scopes = Vec::new();
15847    let mut pending = vec![tree.root_node()];
15848    while let Some(node) = pending.pop() {
15849        if node.kind() == "using_declaration" {
15850            let Some(imported) = node.named_child(0) else {
15851                continue;
15852            };
15853            let Some(mut components) = cpp_type_name_components(imported, source) else {
15854                continue;
15855            };
15856            if components.pop().as_deref() == Some(member) && !components.is_empty() {
15857                scopes.push(components.join("::"));
15858            }
15859            continue;
15860        }
15861        push_named_children_reversed(node, &mut pending);
15862    }
15863    scopes
15864}
15865
15866/// Whether a structured using-declaration scope can name `qualified` as an
15867/// ancestor class. The boundary check prevents `Base` from matching
15868/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
15869pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
15870    qualified == scope
15871        || qualified
15872            .strip_suffix(scope)
15873            .is_some_and(|prefix| prefix.ends_with("::"))
15874}
15875
15876/// Whether `node` is the direct structured type payload of a template
15877/// argument. This role remains meaningful even when a surrounding expression
15878/// is below tree-sitter recovery, because both the `template_argument_list`
15879/// and the `type_descriptor` retain their named fields.
15880pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
15881    let Some(type_descriptor) = node.parent() else {
15882        return false;
15883    };
15884    if type_descriptor.kind() != "type_descriptor"
15885        || type_descriptor.child_by_field_name("type") != Some(node)
15886    {
15887        return false;
15888    }
15889    let Some(arguments) = type_descriptor.parent() else {
15890        return false;
15891    };
15892    if arguments.kind() != "template_argument_list" {
15893        return false;
15894    }
15895    arguments.parent().is_some_and(|parent| {
15896        matches!(parent.kind(), "template_type" | "template_function")
15897            && parent.child_by_field_name("arguments") == Some(arguments)
15898    })
15899}
15900
15901pub fn cpp_template_reference_arguments(
15902    mut node: Node<'_>,
15903    source: &str,
15904) -> Option<Vec<CppTemplateExpression>> {
15905    loop {
15906        match node.kind() {
15907            "template_type" | "template_function" => {
15908                let arguments = node.child_by_field_name("arguments")?;
15909                let mut cursor = arguments.walk();
15910                return Some(
15911                    arguments
15912                        .named_children(&mut cursor)
15913                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
15914                        .map(|argument| CppTemplateExpression {
15915                            text: normalize_cpp_whitespace(node_text(argument, source)),
15916                            // One template term from a resolver query; see `ParentIndex::unindexed`.
15917                            term: cpp_template_term(
15918                                argument,
15919                                source,
15920                                &[],
15921                                &ParentIndex::unindexed(),
15922                            ),
15923                        })
15924                        .collect(),
15925                );
15926            }
15927            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
15928                node = node
15929                    .child_by_field_name("name")
15930                    .or_else(|| node.child_by_field_name("type"))?;
15931            }
15932            _ => return None,
15933        }
15934    }
15935}
15936
15937fn cpp_reconcile_primary_template_parameters(
15938    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
15939    preferred: &CodeUnit,
15940) -> Option<Vec<CppTemplateParameterMetadata>> {
15941    let canonical = candidates
15942        .iter()
15943        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
15944    let mut merged = canonical
15945        .parameters
15946        .iter()
15947        .map(|parameter| CppTemplateParameterMetadata {
15948            name: parameter.name.clone(),
15949            kind: parameter.kind,
15950            variadic: parameter.variadic,
15951            default: None,
15952        })
15953        .collect::<Vec<_>>();
15954
15955    for (_, metadata) in candidates {
15956        if metadata.parameters.len() != merged.len() {
15957            return None;
15958        }
15959        let rename_bindings = metadata
15960            .parameters
15961            .iter()
15962            .zip(&merged)
15963            .map(|(parameter, canonical)| {
15964                (
15965                    parameter.name.clone(),
15966                    CppTemplateTerm::Parameter(canonical.name.clone()),
15967                )
15968            })
15969            .collect::<HashMap<_, _>>();
15970        for ((parameter, canonical), merged_parameter) in metadata
15971            .parameters
15972            .iter()
15973            .zip(&canonical.parameters)
15974            .zip(&mut merged)
15975        {
15976            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
15977                return None;
15978            }
15979            let Some(default) = &parameter.default else {
15980                continue;
15981            };
15982            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
15983            if let Some(existing) = &merged_parameter.default {
15984                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
15985                    return None;
15986                }
15987            } else {
15988                merged_parameter.default = Some(CppTemplateExpression {
15989                    text: default.text.clone(),
15990                    term: normalized_term,
15991                });
15992            }
15993        }
15994    }
15995    Some(merged)
15996}
15997
15998pub fn cpp_bind_template_arguments(
15999    parameters: &[CppTemplateParameterMetadata],
16000    explicit_arguments: &[CppTemplateExpression],
16001) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
16002    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
16003    if variadic_index.is_some_and(|index| {
16004        index + 1 != parameters.len()
16005            || parameters[index + 1..]
16006                .iter()
16007                .any(|parameter| parameter.variadic)
16008    }) {
16009        return None;
16010    }
16011    let fixed_count = variadic_index.unwrap_or(parameters.len());
16012    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
16013        return None;
16014    }
16015    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
16016    let mut expanded = explicit_arguments[..explicit_fixed_count]
16017        .iter()
16018        .map(cpp_clone_template_expression_iterative)
16019        .collect::<Vec<_>>();
16020    let mut bindings = HashMap::default();
16021    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
16022        bindings.insert(
16023            parameter.name.clone(),
16024            cpp_clone_template_term_iterative(&argument.term),
16025        );
16026    }
16027    for parameter in &parameters[explicit_fixed_count..fixed_count] {
16028        let default = parameter.default.as_ref()?;
16029        let term = cpp_substitute_template_term(&default.term, &bindings)?;
16030        bindings.insert(parameter.name.clone(), term.clone());
16031        expanded.push(CppTemplateExpression {
16032            text: default.text.clone(),
16033            term,
16034        });
16035    }
16036    if let Some(index) = variadic_index {
16037        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
16038        expanded.extend(
16039            packed_arguments
16040                .iter()
16041                .map(cpp_clone_template_expression_iterative),
16042        );
16043        bindings.insert(
16044            parameters[index].name.clone(),
16045            CppTemplateTerm::Node {
16046                kind: "parameter_pack".to_string(),
16047                children: packed_arguments
16048                    .iter()
16049                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
16050                    .collect(),
16051            },
16052        );
16053    }
16054    Some((expanded, bindings))
16055}
16056
16057fn cpp_specialization_matches(
16058    metadata: &CppTemplateMetadata,
16059    arguments: &[CppTemplateExpression],
16060) -> bool {
16061    if metadata.specialization_arguments.len() != arguments.len() {
16062        return false;
16063    }
16064    let parameter_names = metadata
16065        .parameters
16066        .iter()
16067        .map(|parameter| parameter.name.as_str())
16068        .collect::<HashSet<_>>();
16069    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16070    for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
16071        if !cpp_unify_template_term(
16072            &pattern.term,
16073            &argument.term,
16074            &parameter_names,
16075            &mut bindings,
16076        ) {
16077            return false;
16078        }
16079    }
16080    true
16081}
16082
16083fn cpp_specialization_more_specialized(
16084    candidate: &CppTemplateMetadata,
16085    other: &CppTemplateMetadata,
16086) -> bool {
16087    cpp_specialization_pattern_accepts(other, candidate)
16088        && !cpp_specialization_pattern_accepts(candidate, other)
16089}
16090
16091fn cpp_specialization_pattern_accepts(
16092    broader: &CppTemplateMetadata,
16093    narrower: &CppTemplateMetadata,
16094) -> bool {
16095    if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
16096        return false;
16097    }
16098    let parameter_names = broader
16099        .parameters
16100        .iter()
16101        .map(|parameter| parameter.name.as_str())
16102        .collect::<HashSet<_>>();
16103    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16104    broader
16105        .specialization_arguments
16106        .iter()
16107        .zip(&narrower.specialization_arguments)
16108        .all(|(pattern, argument)| {
16109            cpp_unify_template_term(
16110                &pattern.term,
16111                &argument.term,
16112                &parameter_names,
16113                &mut bindings,
16114            )
16115        })
16116}
16117
16118pub fn cpp_substitute_template_term(
16119    term: &CppTemplateTerm,
16120    bindings: &HashMap<String, CppTemplateTerm>,
16121) -> Option<CppTemplateTerm> {
16122    enum Work<'a> {
16123        Visit(&'a CppTemplateTerm),
16124        Build { kind: String, child_count: usize },
16125    }
16126
16127    let mut work = vec![Work::Visit(term)];
16128    let mut substituted = Vec::new();
16129    while let Some(next) = work.pop() {
16130        match next {
16131            Work::Visit(CppTemplateTerm::Parameter(name)) => {
16132                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
16133            }
16134            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16135                substituted.push(CppTemplateTerm::Atom {
16136                    kind: kind.clone(),
16137                    text: text.clone(),
16138                });
16139            }
16140            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16141                work.push(Work::Build {
16142                    kind: kind.clone(),
16143                    child_count: children.len(),
16144                });
16145                work.extend(children.iter().rev().map(Work::Visit));
16146            }
16147            Work::Build { kind, child_count } => {
16148                let children = substituted.split_off(substituted.len() - child_count);
16149                substituted.push(CppTemplateTerm::Node { kind, children });
16150            }
16151        }
16152    }
16153    substituted.pop()
16154}
16155
16156pub fn cpp_substitute_template_arguments(
16157    arguments: &[CppTemplateExpression],
16158    bindings: &HashMap<String, CppTemplateTerm>,
16159) -> Option<Vec<CppTemplateExpression>> {
16160    let mut substituted = Vec::new();
16161    for argument in arguments {
16162        let CppTemplateTerm::Node { kind, children } = &argument.term else {
16163            substituted.push(CppTemplateExpression {
16164                text: argument.text.clone(),
16165                term: cpp_substitute_template_term(&argument.term, bindings)?,
16166            });
16167            continue;
16168        };
16169        if kind != "parameter_pack_expansion" {
16170            substituted.push(CppTemplateExpression {
16171                text: argument.text.clone(),
16172                term: cpp_substitute_template_term(&argument.term, bindings)?,
16173            });
16174            continue;
16175        }
16176        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
16177            return None;
16178        };
16179        if ellipsis != "..." {
16180            return None;
16181        }
16182
16183        let mut pack_names = Vec::new();
16184        let mut work = vec![pattern];
16185        while let Some(term) = work.pop() {
16186            match term {
16187                CppTemplateTerm::Parameter(name)
16188                    if matches!(
16189                        bindings.get(name),
16190                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
16191                    ) =>
16192                {
16193                    if !pack_names.contains(name) {
16194                        pack_names.push(name.clone());
16195                    }
16196                }
16197                CppTemplateTerm::Node { children, .. } => work.extend(children),
16198                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16199            }
16200        }
16201        let first_pack = pack_names.first()?;
16202        let CppTemplateTerm::Node {
16203            children: first_elements,
16204            ..
16205        } = bindings.get(first_pack)?
16206        else {
16207            return None;
16208        };
16209        let pack_len = first_elements.len();
16210        for pack_name in &pack_names {
16211            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16212                return None;
16213            };
16214            if children.len() != pack_len {
16215                return None;
16216            }
16217        }
16218        for index in 0..pack_len {
16219            let mut element_bindings = bindings.clone();
16220            for pack_name in &pack_names {
16221                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16222                    return None;
16223                };
16224                element_bindings.insert(
16225                    pack_name.clone(),
16226                    cpp_clone_template_term_iterative(&children[index]),
16227                );
16228            }
16229            substituted.push(CppTemplateExpression {
16230                text: argument.text.clone(),
16231                term: cpp_substitute_template_term(pattern, &element_bindings)?,
16232            });
16233        }
16234    }
16235    Some(substituted)
16236}
16237
16238fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
16239    enum Work<'a> {
16240        Visit(&'a CppTemplateTerm),
16241        Build { kind: String, child_count: usize },
16242    }
16243
16244    let mut work = vec![Work::Visit(term)];
16245    let mut cloned = Vec::new();
16246    while let Some(next) = work.pop() {
16247        match next {
16248            Work::Visit(CppTemplateTerm::Parameter(name)) => {
16249                cloned.push(CppTemplateTerm::Parameter(name.clone()));
16250            }
16251            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16252                cloned.push(CppTemplateTerm::Atom {
16253                    kind: kind.clone(),
16254                    text: text.clone(),
16255                });
16256            }
16257            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16258                work.push(Work::Build {
16259                    kind: kind.clone(),
16260                    child_count: children.len(),
16261                });
16262                work.extend(children.iter().rev().map(Work::Visit));
16263            }
16264            Work::Build { kind, child_count } => {
16265                let children = cloned.split_off(cloned.len() - child_count);
16266                cloned.push(CppTemplateTerm::Node { kind, children });
16267            }
16268        }
16269    }
16270    cloned
16271        .pop()
16272        .expect("template term traversal emits one root")
16273}
16274
16275fn cpp_clone_template_expression_iterative(
16276    expression: &CppTemplateExpression,
16277) -> CppTemplateExpression {
16278    CppTemplateExpression {
16279        text: expression.text.clone(),
16280        term: cpp_clone_template_term_iterative(&expression.term),
16281    }
16282}
16283
16284pub fn cpp_unify_template_term(
16285    pattern: &CppTemplateTerm,
16286    argument: &CppTemplateTerm,
16287    parameters: &HashSet<&str>,
16288    bindings: &mut HashMap<String, CppTemplateTerm>,
16289) -> bool {
16290    let mut work = vec![(pattern, argument)];
16291    while let Some((pattern, argument)) = work.pop() {
16292        match pattern {
16293            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
16294                if let Some(bound) = bindings.get(name) {
16295                    if !cpp_template_terms_equal(bound, argument) {
16296                        return false;
16297                    }
16298                } else {
16299                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
16300                }
16301            }
16302            CppTemplateTerm::Atom {
16303                kind: pattern_kind,
16304                text: pattern_text,
16305            } => {
16306                if !matches!(
16307                    argument,
16308                    CppTemplateTerm::Atom { kind, text }
16309                        if kind == pattern_kind && text == pattern_text
16310                ) {
16311                    return false;
16312                }
16313            }
16314            CppTemplateTerm::Node {
16315                kind: pattern_kind,
16316                children: pattern_children,
16317            } => {
16318                let CppTemplateTerm::Node { kind, children } = argument else {
16319                    return false;
16320                };
16321                if kind != pattern_kind || children.len() != pattern_children.len() {
16322                    return false;
16323                }
16324                work.extend(pattern_children.iter().zip(children).rev());
16325            }
16326            CppTemplateTerm::Parameter(_) => return false,
16327        }
16328    }
16329    true
16330}
16331
16332fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
16333    let mut work = vec![(left, right)];
16334    while let Some((left, right)) = work.pop() {
16335        match (left, right) {
16336            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
16337                if left != right {
16338                    return false;
16339                }
16340            }
16341            (
16342                CppTemplateTerm::Atom {
16343                    kind: left_kind,
16344                    text: left_text,
16345                },
16346                CppTemplateTerm::Atom {
16347                    kind: right_kind,
16348                    text: right_text,
16349                },
16350            ) => {
16351                if left_kind != right_kind || left_text != right_text {
16352                    return false;
16353                }
16354            }
16355            (
16356                CppTemplateTerm::Node {
16357                    kind: left_kind,
16358                    children: left_children,
16359                },
16360                CppTemplateTerm::Node {
16361                    kind: right_kind,
16362                    children: right_children,
16363                },
16364            ) => {
16365                if left_kind != right_kind || left_children.len() != right_children.len() {
16366                    return false;
16367                }
16368                work.extend(left_children.iter().zip(right_children).rev());
16369            }
16370            _ => return false,
16371        }
16372    }
16373    true
16374}
16375
16376pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
16377    let mut components = Vec::new();
16378    let mut stack = vec![node];
16379    while let Some(current) = stack.pop() {
16380        match current.kind() {
16381            "identifier"
16382            | "field_identifier"
16383            | "namespace_identifier"
16384            | "type_identifier"
16385            | "operator_name"
16386            | "destructor_name" => components.push(current),
16387            "template_type" | "template_function" => {
16388                stack.push(current.child_by_field_name("name")?);
16389            }
16390            "dependent_name" => stack.push(current.named_child(0)?),
16391            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
16392                stack.push(current.child_by_field_name("name")?);
16393                if let Some(scope) = current.child_by_field_name("scope") {
16394                    stack.push(scope);
16395                }
16396            }
16397            "nested_namespace_specifier" => {
16398                for index in (0..current.named_child_count()).rev() {
16399                    stack.push(current.named_child(index)?);
16400                }
16401            }
16402            _ => return None,
16403        }
16404    }
16405    Some(components)
16406}
16407
16408pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
16409    node.child_by_field_name("scope").is_none()
16410        && node.child(0).is_some_and(|child| child.kind() == "::")
16411}
16412
16413fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16414    let mut namespaces = Vec::new();
16415    let mut current = node.parent();
16416    while let Some(parent) = current {
16417        if parent.kind() == "namespace_definition"
16418            && let Some(name) = parent.child_by_field_name("name")
16419        {
16420            let mut components = Vec::new();
16421            append_cpp_name_components(name, source, &mut components)?;
16422            namespaces.push(components);
16423        }
16424        current = parent.parent();
16425    }
16426    namespaces.reverse();
16427    Some(namespaces.into_iter().flatten().collect())
16428}
16429
16430/// Whether a parser-derived namespace path can be reconciled with an indexed
16431/// owner scope without inventing an unrelated short-name binding.
16432///
16433/// Macro namespace sentinels can make tree-sitter omit one or more namespace
16434/// definitions from the ancestor chain. Preserve the order of every namespace
16435/// that did survive parsing, but allow indexed components between them. An
16436/// empty path is accepted only when the declarator itself supplies a nested
16437/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
16438/// owner chain, that suffix is structural evidence that a namespace was lost.
16439/// A one-segment owner at the translation-unit root remains insufficient.
16440fn indexed_namespace_path_is_recoverable(
16441    lexical_scope: &[String],
16442    indexed_owner_scope: &[String],
16443    explicit_owner_component_count: usize,
16444) -> bool {
16445    if lexical_scope.is_empty() {
16446        return explicit_owner_component_count > 1;
16447    }
16448    if lexical_scope.len() >= indexed_owner_scope.len() {
16449        return false;
16450    }
16451    let mut indexed = indexed_owner_scope.iter();
16452    lexical_scope
16453        .iter()
16454        .all(|component| indexed.any(|candidate| candidate == component))
16455}
16456
16457pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
16458    let mut current = node.parent();
16459    while let Some(parent) = current {
16460        if parent.kind() == kind {
16461            return true;
16462        }
16463        current = parent.parent();
16464    }
16465    false
16466}
16467
16468/// Whether a declaration type is initialized with a pointer cast.
16469///
16470/// This structured shape has an independent qualified occurrence in addition
16471/// to the cast descriptor below it. Other declarations must keep their normal
16472/// full-range occurrence only.
16473pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
16474    let mut current = Some(node);
16475    while let Some(candidate) = current {
16476        if candidate.kind() == "declaration" {
16477            let Some(type_node) = candidate.child_by_field_name("type") else {
16478                return false;
16479            };
16480            if !(type_node.start_byte() <= node.start_byte()
16481                && node.end_byte() <= type_node.end_byte())
16482            {
16483                return false;
16484            }
16485            let mut cursor = candidate.walk();
16486            return candidate.named_children(&mut cursor).any(|child| {
16487                child.kind() == "init_declarator"
16488                    && child
16489                        .child_by_field_name("value")
16490                        .is_some_and(|value| value.kind() == "cast_expression")
16491            });
16492        }
16493        current = candidate.parent();
16494    }
16495    false
16496}
16497
16498#[derive(Clone, Copy, PartialEq, Eq)]
16499pub(crate) enum QualifiedAliasReferenceKind {
16500    Ordinary,
16501    ConstructorWithExpressionArgument,
16502    ExhaustiveTemplate,
16503}
16504
16505/// Whether a qualified alias reference preserves the requested target.
16506///
16507/// The complete qualified spelling and its terminal identifier are both valid
16508/// occurrences when the visible alias path is structurally proven to name the
16509/// target. Template aliases use their bound arguments; ordinary aliases use
16510/// their structured primary chain.
16511pub(crate) fn qualified_alias_reference_preserves_target(
16512    node: Node<'_>,
16513    target: &CodeUnit,
16514    analyzer: &CppGraphSource<'_>,
16515    visibility: &VisibilityIndex<'_>,
16516    file: &ProjectFile,
16517    source: &str,
16518) -> Option<QualifiedAliasReferenceKind> {
16519    if !matches!(
16520        node.kind(),
16521        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16522    ) {
16523        return None;
16524    }
16525    let components = cpp_type_name_components(node, source)?;
16526    let name = components.last()?;
16527    analyzer.type_alias_provider().and_then(|provider| {
16528        visibility
16529            .visible_identifier_candidates(file, name)
16530            .find_map(|candidate| {
16531                let proof = provider.is_type_alias(candidate)
16532                    && canonical_cpp_scope_components(candidate) == components
16533                    && visibility.external_type_candidate_visible_in_context(
16534                        analyzer, file, candidate, node,
16535                    )
16536                    && match cpp_template_reference_arguments(node, source) {
16537                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
16538                            analyzer, file, candidate, &arguments, target,
16539                        ),
16540                        None => visibility.structured_alias_primary_preserves_target(
16541                            analyzer, file, candidate, target,
16542                        ),
16543                    };
16544                proof.then(|| {
16545                    if cpp_template_reference_arguments(node, source).is_some()
16546                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
16547                            analyzer, file, candidate,
16548                        )
16549                    {
16550                        QualifiedAliasReferenceKind::ExhaustiveTemplate
16551                    } else if qualified_alias_constructor_has_expression_argument(node)
16552                        || qualified_alias_local_constructor_declaration(node)
16553                    {
16554                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16555                    } else {
16556                        QualifiedAliasReferenceKind::Ordinary
16557                    }
16558                })
16559            })
16560    })
16561}
16562
16563pub(crate) fn qualified_alias_reference_requires_terminal(
16564    reference: Option<QualifiedAliasReferenceKind>,
16565) -> bool {
16566    matches!(
16567        reference,
16568        Some(
16569            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16570                | QualifiedAliasReferenceKind::ExhaustiveTemplate
16571        )
16572    )
16573}
16574
16575fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
16576    let Some(declaration) = node.parent().filter(|parent| {
16577        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16578    }) else {
16579        return false;
16580    };
16581    let mut cursor = declaration.walk();
16582    declaration.named_children(&mut cursor).any(|child| {
16583        child.kind() == "init_declarator"
16584            && child
16585                .child_by_field_name("value")
16586                .filter(|value| value.kind() == "argument_list")
16587                .is_some_and(|arguments| {
16588                    let mut cursor = arguments.walk();
16589                    arguments.named_children(&mut cursor).any(|argument| {
16590                        let is_parameter = matches!(
16591                            argument.kind(),
16592                            "parameter_declaration" | "optional_parameter_declaration"
16593                        );
16594                        if is_parameter {
16595                            argument
16596                                .child_by_field_name("type")
16597                                .is_some_and(|type_node| {
16598                                    type_node.kind() == "type_identifier"
16599                                        && argument.child_by_field_name("declarator").is_none()
16600                                })
16601                        } else {
16602                            !argument.kind().ends_with("_literal")
16603                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
16604                        }
16605                    })
16606                })
16607    })
16608}
16609
16610/// Tree-sitter represents a local C++ direct construction such as
16611/// `Alias value(argument)` as a function declarator. Restrict that recovery to
16612/// declarations inside a compound statement so namespace-scope function
16613/// declarations with the same qualified return type stay full-range only.
16614fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
16615    let Some(declaration) = node.parent().filter(|parent| {
16616        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16617    }) else {
16618        return false;
16619    };
16620    if declaration
16621        .parent()
16622        .is_none_or(|parent| parent.kind() != "compound_statement")
16623    {
16624        return false;
16625    }
16626    let mut cursor = declaration.walk();
16627    declaration
16628        .named_children(&mut cursor)
16629        .any(|child| child.kind() == "function_declarator")
16630}
16631
16632/// Return the terminal identifier represented by a callable or type callee.
16633///
16634/// Qualified, scoped, template, and field wrappers are traversed through their
16635/// grammar fields so both function calls and type constructions emit the token
16636/// that names the referenced declaration.
16637pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
16638    loop {
16639        let next = match node.kind() {
16640            "qualified_identifier"
16641            | "scoped_identifier"
16642            | "template_method"
16643            | "template_function"
16644            | "template_type" => node.child_by_field_name("name"),
16645            "field_expression" => node.child_by_field_name("field"),
16646            _ => None,
16647        };
16648        let Some(next) = next else {
16649            return node;
16650        };
16651        node = next;
16652    }
16653}
16654
16655#[derive(Clone, Copy)]
16656pub struct RecoveredRelationalTemplateMemberCall<'tree> {
16657    pub receiver: Node<'tree>,
16658    pub member: Node<'tree>,
16659    pub arity: usize,
16660}
16661
16662/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
16663/// nested relational expressions instead of a `template_method` call.
16664///
16665/// The recovery uses only grammar fields: the selected field must be the left
16666/// side of `<`, that expression must be the left side of `>`, and the right
16667/// side of `>` must be the parenthesized call arguments. Semantic callers must
16668/// additionally prove the receiver owner and the member's template status.
16669pub fn recovered_relational_template_member_call(
16670    field: Node<'_>,
16671) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
16672    if field.kind() != "field_expression" {
16673        return None;
16674    }
16675    let receiver = field
16676        .child_by_field_name("argument")
16677        .or_else(|| field.child_by_field_name("object"))?;
16678    let member = field.child_by_field_name("field")?;
16679    let less = field.parent()?;
16680    if less.kind() != "binary_expression"
16681        || less.child_by_field_name("left") != Some(field)
16682        || less
16683            .child_by_field_name("operator")
16684            .is_none_or(|operator| operator.kind() != "<")
16685        || less.child_by_field_name("right").is_none()
16686    {
16687        return None;
16688    }
16689    let greater = less.parent()?;
16690    if greater.kind() != "binary_expression"
16691        || greater.child_by_field_name("left") != Some(less)
16692        || greater
16693            .child_by_field_name("operator")
16694            .is_none_or(|operator| operator.kind() != ">")
16695    {
16696        return None;
16697    }
16698    let arguments = greater.child_by_field_name("right")?;
16699    if arguments.kind() != "parenthesized_expression" {
16700        return None;
16701    }
16702    let arity = parenthesized_call_argument_arity(arguments)?;
16703    Some(RecoveredRelationalTemplateMemberCall {
16704        receiver,
16705        member,
16706        arity,
16707    })
16708}
16709
16710fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
16711    let expression = arguments.named_child(0)?;
16712    if expression.kind() != "comma_expression" {
16713        return Some(1);
16714    }
16715    let mut arity = 0usize;
16716    let mut stack = vec![expression];
16717    while let Some(node) = stack.pop() {
16718        if node.kind() == "comma_expression" {
16719            stack.push(node.child_by_field_name("right")?);
16720            stack.push(node.child_by_field_name("left")?);
16721        } else {
16722            arity += 1;
16723        }
16724    }
16725    Some(arity)
16726}
16727
16728/// Whether `node` is part of a call's callee expression, walking only through
16729/// the grammar wrappers that can structurally contain that callee.
16730pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
16731    while let Some(parent) = node.parent() {
16732        match parent.kind() {
16733            "call_expression" => {
16734                return parent
16735                    .child_by_field_name("function")
16736                    .or_else(|| parent.named_child(0))
16737                    == Some(node);
16738            }
16739            "qualified_identifier"
16740            | "scoped_identifier"
16741            | "template_function"
16742            | "template_type"
16743            | "field_expression" => node = parent,
16744            _ => return false,
16745        }
16746    }
16747    false
16748}
16749
16750pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
16751    if is_call_callee_node(node) {
16752        function_terminal_node(node)
16753    } else {
16754        node
16755    }
16756}
16757
16758pub fn normalize_type_text(value: &str) -> String {
16759    strip_tag_type_prefix(
16760        normalize_cpp_whitespace(value)
16761            .trim_start_matches("const ")
16762            .trim_end_matches('*')
16763            .trim_end_matches('&')
16764            .trim(),
16765    )
16766    .to_string()
16767}
16768
16769fn strip_tag_type_prefix(value: &str) -> &str {
16770    let value = value.trim_start_matches("const ");
16771    value
16772        .strip_prefix("struct ")
16773        .or_else(|| value.strip_prefix("class "))
16774        .or_else(|| value.strip_prefix("enum "))
16775        .unwrap_or(value)
16776        .trim()
16777}
16778
16779pub fn normalize_reference_name(value: &str) -> Option<String> {
16780    let normalized = normalize_cpp_reference_text(value);
16781    (!normalized.is_empty()).then_some(normalized)
16782}
16783
16784pub fn normalize_cpp_reference_text(value: &str) -> String {
16785    let mut text = normalize_cpp_whitespace(value)
16786        .trim_start_matches("new ")
16787        .trim()
16788        .to_string();
16789    if let Some(index) = text.find(['(', '{']) {
16790        text.truncate(index);
16791    }
16792    if let Some(index) = text.find('<') {
16793        text.truncate(index);
16794    }
16795    let normalized = text
16796        .trim()
16797        .trim_start_matches("const ")
16798        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
16799        .trim_matches(':')
16800        .trim();
16801    strip_tag_type_prefix(normalized).to_string()
16802}
16803
16804pub fn cpp_name_for(unit: &CodeUnit) -> String {
16805    let short = unit.short_name().replace(['.', '$'], "::");
16806    if unit.package_name().is_empty() {
16807        short
16808    } else {
16809        format!("{}::{}", unit.package_name(), short)
16810    }
16811}
16812
16813/// Render an indexed C++ qualified name from its authoritative FqName
16814/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
16815/// that belong to a template argument (for example `Args...`).
16816fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
16817    let fq = unit.fq();
16818    if fq.is_empty() {
16819        return None;
16820    }
16821    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16822    Some(
16823        fq.segments()
16824            .iter()
16825            .map(|&segment| interner.resolve(segment).0)
16826            .collect::<Vec<_>>()
16827            .join("::"),
16828    )
16829}
16830
16831fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
16832    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
16833        || unit.fq().is_empty() && cpp_name_for(unit) == expected
16834}
16835
16836/// Return the indexed C++ owner scope without reparsing its rendered name.
16837///
16838/// Template spellings are opaque within an indexed `FqName` segment.  In
16839/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
16840/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
16841/// through `parse_symbol_path` would mistake those dots for component
16842/// separators.  Cache-loaded/legacy units may still have an empty structured
16843/// name, so retain the parser only as that explicit fallback.
16844pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
16845    let fq = unit.fq();
16846    if !fq.is_empty() {
16847        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16848        let scope = fq
16849            .segments()
16850            .iter()
16851            .filter_map(|&segment| {
16852                let (text, kind) = interner.resolve(segment);
16853                matches!(
16854                    kind,
16855                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
16856                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
16857                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
16858                )
16859                .then(|| text.to_string())
16860            })
16861            .collect();
16862        return scope;
16863    }
16864    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16865        brokk_bifrost_core::analyzer::Language::Cpp,
16866        &cpp_name_for(unit),
16867    )
16868}
16869
16870// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
16871// (not the substring "->"), which deliberately reduces an `operator->`-style
16872// terminal segment to an empty tail rather than keeping it intact; the shared
16873// structured splitter's cpp operator-token merge would keep `operator->`
16874// whole instead, changing this function's result — `name_matches_callable`'s
16875// `expected.starts_with("operator")` fallback exists specifically to
16876// compensate for that reduction, and a pinned regression test
16877// (`operator-> must not be reduced with terminal_name-style punctuation
16878// splitting`) asserts today's char-class behavior. Not equivalence-provable;
16879// revisit alongside that pinned test if it is ever relaxed.
16880pub fn terminal_name(value: &str) -> &str {
16881    value
16882        .rsplit("::")
16883        .next()
16884        .unwrap_or(value)
16885        .rsplit(['.', '-', '>'])
16886        .next()
16887        .unwrap_or(value)
16888        .trim()
16889}
16890
16891pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
16892    terminal_name(&normalize_cpp_reference_text(value)) == expected
16893}
16894
16895pub fn name_matches_callable(value: &str, expected: &str) -> bool {
16896    name_matches_terminal(value, expected)
16897        || expected.starts_with("operator")
16898            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
16899}
16900
16901pub fn name_mentions(value: &str, expected: &str) -> bool {
16902    normalize_cpp_reference_text(value)
16903        .split("::")
16904        .any(|part| part == expected)
16905}
16906
16907pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
16908    let cpp_name = cpp_name_for(unit);
16909    if reference.contains("::") {
16910        return reference == cpp_name;
16911    }
16912    reference == cpp_name
16913        || terminal_name(reference) == unit.identifier()
16914            && (unit.package_name().is_empty() || reference == unit.identifier())
16915}
16916
16917pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
16918    match kind {
16919        TargetKind::Type
16920        | TargetKind::Constructor
16921        | TargetKind::Method
16922        | TargetKind::MemberField => true,
16923        TargetKind::FreeFunction => unit.is_function(),
16924        TargetKind::GlobalField => unit.is_field(),
16925        TargetKind::Macro => unit.is_macro(),
16926    }
16927}
16928
16929pub fn is_type_alias(unit: &CodeUnit) -> bool {
16930    unit.kind() == CodeUnitType::Field
16931        && unit.signature().is_some_and(|signature| {
16932            signature.starts_with("typedef ") || signature.starts_with("using ")
16933        })
16934}
16935
16936fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
16937    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
16938    let target_name = cpp_name_for(target);
16939    if normalized.contains("::") {
16940        return normalized == target_name;
16941    }
16942    if let Some(namespace) = alias.namespace.as_deref() {
16943        return namespace_prefixes(namespace)
16944            .into_iter()
16945            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
16946    }
16947    target.package_name().is_empty() && normalized == target.identifier()
16948}
16949
16950/// The declared return type text of a C++ function unit, with leading declaration specifiers
16951/// stripped, e.g. `T*` for `T* operator->()`.
16952pub fn cpp_function_return_type_text(
16953    analyzer: &CppGraphSource<'_>,
16954    function: &CodeUnit,
16955) -> Option<String> {
16956    let metadata = analyzer.signature_metadata(function);
16957    if !metadata.is_empty() {
16958        let first = metadata.first()?.return_type_text()?;
16959        return metadata
16960            .iter()
16961            .all(|metadata| metadata.return_type_text() == Some(first))
16962            .then(|| first.to_string());
16963    }
16964    let signature = cpp_function_signature_text(analyzer, function)?;
16965    cpp_function_return_type_text_from_signature(&signature)
16966}
16967
16968fn cpp_function_signature_text(
16969    analyzer: &CppGraphSource<'_>,
16970    function: &CodeUnit,
16971) -> Option<String> {
16972    function
16973        .signature()
16974        .filter(|signature| signature.contains(function.identifier()))
16975        .map(str::to_string)
16976        .or_else(|| analyzer.signatures(function).first().cloned())
16977        .or_else(|| analyzer.get_source(function, false))
16978}
16979
16980fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
16981    let open = signature.find('(')?;
16982    let name_at = cpp_function_name_start(signature, open)?;
16983    if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
16984        return Some(return_type);
16985    }
16986    let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
16987        .split_whitespace()
16988        .filter(|token| {
16989            !matches!(
16990                *token,
16991                "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
16992            )
16993        })
16994        .collect::<Vec<_>>()
16995        .join(" ");
16996    let type_text = type_text.trim();
16997    (!type_text.is_empty()).then(|| type_text.to_string())
16998}
16999
17000fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
17001    let before_parameters = &signature[..open];
17002    if let Some(operator_at) = before_parameters.rfind("operator") {
17003        let boundary = operator_at == 0
17004            || before_parameters[..operator_at]
17005                .chars()
17006                .next_back()
17007                .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
17008        if boundary {
17009            return Some(operator_at);
17010        }
17011    }
17012    before_parameters
17013        .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
17014        .map(|index| index + 1)
17015}
17016
17017fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
17018    let open = signature_from_name.find('(')?;
17019    let mut depth = 0i32;
17020    for (offset, ch) in signature_from_name[open..].char_indices() {
17021        match ch {
17022            '(' => depth += 1,
17023            ')' => {
17024                depth -= 1;
17025                if depth == 0 {
17026                    let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
17027                    let arrow = rest.find("->")?;
17028                    let return_type = rest[arrow + 2..].trim_start();
17029                    let return_type = return_type
17030                        .split(['{', ';'])
17031                        .next()
17032                        .unwrap_or(return_type)
17033                        .trim();
17034                    return (!return_type.is_empty()).then(|| return_type.to_string());
17035                }
17036            }
17037            _ => {}
17038        }
17039    }
17040    None
17041}
17042
17043/// Strip a leading `template <...>` parameter clause, leaving the declaration that follows.
17044/// Returns the input unchanged when there is no such clause.
17045fn cpp_strip_leading_template_clause(text: &str) -> &str {
17046    let trimmed = text.trim_start();
17047    let Some(rest) = trimmed.strip_prefix("template") else {
17048        return text;
17049    };
17050    let rest = rest.trim_start();
17051    if !rest.starts_with('<') {
17052        return text;
17053    }
17054    let mut depth = 0i32;
17055    for (offset, ch) in rest.char_indices() {
17056        match ch {
17057            '<' => depth += 1,
17058            '>' => {
17059                depth -= 1;
17060                if depth == 0 {
17061                    return rest[offset + ch.len_utf8()..].trim_start();
17062                }
17063            }
17064            _ => {}
17065        }
17066    }
17067    text
17068}
17069
17070pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
17071    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
17072    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
17073    // the same string `default_parent_fq_name`/`fq().parent()` would render:
17074    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
17075    // `::`) between a trailing `Package` segment and a following `Type`
17076    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
17077    // popping the unit's own `fq()` segment would NOT reproduce this
17078    // fully-`::`-joined string. Left as a split on the locally-built
17079    // all-colon string rather than the unit's structured name.
17080    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
17081        namespace
17082            .strip_prefix("anonymous_namespace::")
17083            .unwrap_or(namespace)
17084            .to_string()
17085    })
17086}
17087
17088fn namespace_prefixes(namespace: &str) -> Vec<String> {
17089    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
17090    // non-`::` separator already converted to `::`, so re-tokenizing it with
17091    // the shared structured splitter and progressively popping the last
17092    // component reproduces the `rsplit_once("::")` outward walk exactly (same
17093    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
17094    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17095        brokk_bifrost_core::analyzer::Language::Cpp,
17096        namespace,
17097    );
17098    let mut prefixes = Vec::new();
17099    while !parts.is_empty() {
17100        prefixes.push(parts.join("::"));
17101        parts.pop();
17102    }
17103    prefixes
17104}
17105
17106fn nearest_namespace_candidates(
17107    candidates: Vec<CodeUnit>,
17108    normalized: &str,
17109    lexical_namespace: Option<&str>,
17110) -> Vec<CodeUnit> {
17111    if normalized.contains("::") {
17112        return candidates;
17113    }
17114    if let Some(namespace) = lexical_namespace {
17115        for prefix in namespace_prefixes(namespace) {
17116            let scoped = candidates
17117                .iter()
17118                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
17119                .cloned()
17120                .collect::<Vec<_>>();
17121            if !scoped.is_empty() {
17122                return scoped;
17123            }
17124        }
17125    }
17126    candidates
17127        .into_iter()
17128        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
17129        .collect()
17130}
17131
17132pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
17133    let mut namespaces = Vec::new();
17134    let mut current = node.parent();
17135    while let Some(parent) = current {
17136        if parent.kind() == "namespace_definition"
17137            && let Some(name) = parent.child_by_field_name("name")
17138        {
17139            let namespace = normalize_cpp_reference_text(node_text(name, source));
17140            if !namespace.is_empty() {
17141                namespaces.push(namespace);
17142            }
17143        }
17144        current = parent.parent();
17145    }
17146    if namespaces.is_empty() {
17147        None
17148    } else {
17149        namespaces.reverse();
17150        Some(namespaces.join("::"))
17151    }
17152}
17153
17154/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
17155/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
17156/// globals rather than members.
17157pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17158    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
17159}
17160
17161fn type_owner_resolution(
17162    analyzer: &CppGraphSource<'_>,
17163    code_unit: &CodeUnit,
17164) -> Option<ResolvedTypeOwner> {
17165    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
17166}
17167
17168fn target_type_owner_resolution(
17169    analyzer: &CppGraphSource<'_>,
17170    code_unit: &CodeUnit,
17171) -> Option<ResolvedTypeOwner> {
17172    match type_owner_resolution(analyzer, code_unit) {
17173        Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
17174        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
17175    }
17176}
17177
17178/// Recover method identity for an indexed out-of-line definition when the
17179/// ordinary parent edge is absent. Prefer the unique include-visible forward
17180/// declaration, then classify exact-FQN class declarations elsewhere in the
17181/// workspace. A unique complete declaration wins; otherwise multiple forward
17182/// declarations are one owner only when they all share one logical identity.
17183/// The qualified callable FQN proves that owner spelling even when its defining
17184/// header is outside the scan file's include closure, while unknown or competing
17185/// complete declarations remain ambiguous.
17186/// This is deliberately target-only: canonical declaration resolution must
17187/// continue to prefer the callable definition rather than replacing it with
17188/// the recovered owner.
17189fn target_forward_owner_resolution(
17190    analyzer: &CppGraphSource<'_>,
17191    code_unit: &CodeUnit,
17192) -> Option<ResolvedTypeOwner> {
17193    if !code_unit.is_function() {
17194        return None;
17195    }
17196    // A top-level free function has no owner at all, and `FqName::parent`
17197    // answers the empty name rather than `None` for a one-segment identity.
17198    // `default_parent_fq_name`, which this replaced, filtered that case out;
17199    // asking the relational store for the empty name is a batch error that
17200    // fails the whole target frontier.
17201    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
17202    let cpp = analyzer.cpp?;
17203    let mut visible_files = HashSet::default();
17204    collect_include_closure(
17205        analyzer,
17206        cpp.include_target_index(),
17207        code_unit.source(),
17208        &mut visible_files,
17209        None,
17210    );
17211    let candidates = analyzer.workspace_definitions().exact(&owner_name);
17212    let visible_candidates = candidates
17213        .iter()
17214        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17215        .cloned()
17216        .collect::<Vec<_>>();
17217    match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17218        DirectOwnerResolution::UniqueFull(unit) => {
17219            return Some(ResolvedTypeOwner {
17220                unit,
17221                is_forward_declaration: false,
17222            });
17223        }
17224        DirectOwnerResolution::ForwardsOnly(forwards) => {
17225            return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17226                unit: forwards.into_iter().next().unwrap(),
17227                is_forward_declaration: true,
17228            });
17229        }
17230        DirectOwnerResolution::Ambiguous => return None,
17231        DirectOwnerResolution::None => {}
17232    }
17233
17234    let candidates = candidates
17235        .into_iter()
17236        .filter(|candidate| candidate.is_class())
17237        .collect::<Vec<_>>();
17238    let (unit, is_forward_declaration) =
17239        match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17240            DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17241            DirectOwnerResolution::ForwardsOnly(forwards) => {
17242                (unique_logical_forward_owner(forwards)?, true)
17243            }
17244            DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17245        };
17246    Some(ResolvedTypeOwner {
17247        unit,
17248        is_forward_declaration,
17249    })
17250}
17251
17252pub fn precise_parent_of(
17253    analyzer: &CppGraphSource<'_>,
17254    visibility: &VisibilityIndex<'_>,
17255    code_unit: &CodeUnit,
17256) -> Option<CodeUnit> {
17257    visibility.cached_precise_parent_of(analyzer, code_unit)
17258}
17259
17260fn precise_parent_resolution(
17261    analyzer: &CppGraphSource<'_>,
17262    code_unit: &CodeUnit,
17263) -> Option<ResolvedTypeOwner> {
17264    #[cfg(any(test, feature = "test-support"))]
17265    if let Some(cpp) = analyzer.cpp {
17266        cpp.record_cpp_parent_resolution_for_test();
17267    }
17268    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17269        return Some(ResolvedTypeOwner {
17270            unit,
17271            is_forward_declaration: false,
17272        });
17273    }
17274    let fallback = analyzer.parent_of(code_unit);
17275    if !code_unit.owner_is_type_scope() {
17276        return fallback.map(|unit| ResolvedTypeOwner {
17277            unit,
17278            is_forward_declaration: false,
17279        });
17280    }
17281    let owner_fq = code_unit
17282        .fq()
17283        .parent()
17284        .expect("a unit with an owner identifier has a structured parent");
17285    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17286    match same_source_owner(analyzer, code_unit, &owner_candidates) {
17287        DirectOwnerResolution::UniqueFull(owner) => {
17288            return Some(ResolvedTypeOwner {
17289                unit: owner,
17290                is_forward_declaration: false,
17291            });
17292        }
17293        DirectOwnerResolution::Ambiguous => return None,
17294        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17295    }
17296    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17297        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17298            unit: owner,
17299            is_forward_declaration: false,
17300        }),
17301        DirectOwnerResolution::Ambiguous => None,
17302        DirectOwnerResolution::ForwardsOnly(forwards) => {
17303            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17304                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17305                    unit: owner,
17306                    is_forward_declaration: false,
17307                }),
17308                FullOwnerResolution::None => {
17309                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17310                        unit,
17311                        is_forward_declaration: true,
17312                    })
17313                }
17314                FullOwnerResolution::Ambiguous => None,
17315            }
17316        }
17317        DirectOwnerResolution::None => {
17318            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17319                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17320                    unit: owner,
17321                    is_forward_declaration: false,
17322                }),
17323                FullOwnerResolution::Ambiguous => None,
17324                FullOwnerResolution::None => fallback
17325                    .filter(|parent| {
17326                        parent.source() == code_unit.source()
17327                            && parent.fq() == &owner_fq
17328                            && (!parent.is_class()
17329                                || cpp_class_declaration_strength(analyzer, parent)
17330                                    == CppClassDeclarationStrength::Full)
17331                    })
17332                    .map(|unit| ResolvedTypeOwner {
17333                        unit,
17334                        is_forward_declaration: false,
17335                    }),
17336            }
17337        }
17338    }
17339}
17340
17341fn exact_structural_type_parent(
17342    analyzer: &CppGraphSource<'_>,
17343    code_unit: &CodeUnit,
17344) -> Option<CodeUnit> {
17345    if !code_unit.is_function() && !code_unit.is_field() {
17346        return None;
17347    }
17348    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
17349    let cpp = analyzer.cpp?;
17350    let parent = cpp.structural_parent_of(code_unit)?;
17351    (!parent.is_module()
17352        && parent.source() == code_unit.source()
17353        && parent.package_name() == code_unit.package_name()
17354        && parent.short_name() == encoded_owner)
17355        .then_some(parent)
17356}
17357
17358fn same_source_owner(
17359    analyzer: &CppGraphSource<'_>,
17360    code_unit: &CodeUnit,
17361    owner_candidates: &[CodeUnit],
17362) -> DirectOwnerResolution {
17363    let candidates = owner_candidates
17364        .iter()
17365        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
17366        .cloned()
17367        .collect::<Vec<_>>();
17368    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17369    classify_direct_owner_candidates(analyzer, candidates.into_iter())
17370}
17371
17372fn visible_full_cpp_owner(
17373    analyzer: &CppGraphSource<'_>,
17374    code_unit: &CodeUnit,
17375    owner_candidates: &[CodeUnit],
17376) -> FullOwnerResolution {
17377    let Some(cpp) = analyzer.cpp else {
17378        return FullOwnerResolution::None;
17379    };
17380    let mut visible_files = HashSet::default();
17381    collect_include_closure(
17382        analyzer,
17383        cpp.include_target_index(),
17384        code_unit.source(),
17385        &mut visible_files,
17386        None,
17387    );
17388    let candidates = owner_candidates
17389        .iter()
17390        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17391        .cloned()
17392        .collect::<Vec<_>>();
17393    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17394    let mut full_definition = None;
17395    for candidate in candidates {
17396        match cpp_class_declaration_strength(analyzer, &candidate) {
17397            CppClassDeclarationStrength::Full if full_definition.is_some() => {
17398                return FullOwnerResolution::Ambiguous;
17399            }
17400            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17401            CppClassDeclarationStrength::Forward => {}
17402            CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
17403        }
17404    }
17405    full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
17406}
17407
17408pub enum DirectOwnerResolution {
17409    None,
17410    ForwardsOnly(Vec<CodeUnit>),
17411    UniqueFull(CodeUnit),
17412    Ambiguous,
17413}
17414
17415enum FullOwnerResolution {
17416    None,
17417    Unique(CodeUnit),
17418    Ambiguous,
17419}
17420
17421#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17422pub enum CppClassDeclarationStrength {
17423    Full,
17424    Forward,
17425    Unknown,
17426}
17427
17428fn directly_included_owner(
17429    analyzer: &CppGraphSource<'_>,
17430    code_unit: &CodeUnit,
17431    owner_candidates: &[CodeUnit],
17432) -> DirectOwnerResolution {
17433    let Some(cpp) = analyzer.cpp else {
17434        return DirectOwnerResolution::None;
17435    };
17436    let imports = analyzer.import_statements(code_unit.source());
17437    let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
17438        .into_iter()
17439        .flat_map(|include| {
17440            resolve_include_targets_with_index(
17441                code_unit.source(),
17442                &include,
17443                cpp.include_target_index(),
17444            )
17445        })
17446        .collect();
17447    let candidates = owner_candidates
17448        .iter()
17449        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
17450        .cloned()
17451        .collect::<Vec<_>>();
17452    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17453    classify_direct_owner_candidates(analyzer, candidates.into_iter())
17454}
17455
17456fn prefer_member_declaring_owners(
17457    analyzer: &CppGraphSource<'_>,
17458    member: &CodeUnit,
17459    candidates: Vec<CodeUnit>,
17460) -> Vec<CodeUnit> {
17461    let matching = candidates
17462        .iter()
17463        .filter(|owner| owner_declares_member(analyzer, owner, member))
17464        .cloned()
17465        .collect::<Vec<_>>();
17466    if matching.is_empty() {
17467        candidates
17468    } else {
17469        matching
17470    }
17471}
17472
17473fn owner_declares_member(
17474    analyzer: &CppGraphSource<'_>,
17475    owner: &CodeUnit,
17476    member: &CodeUnit,
17477) -> bool {
17478    analyzer.direct_children(owner).into_iter().any(|child| {
17479        child.kind() == member.kind()
17480            && child.identifier() == member.identifier()
17481            && child.signature() == member.signature()
17482    })
17483}
17484
17485fn classify_direct_owner_candidates(
17486    analyzer: &CppGraphSource<'_>,
17487    candidates: impl Iterator<Item = CodeUnit>,
17488) -> DirectOwnerResolution {
17489    collapse_owner_candidates(candidates.map(|candidate| {
17490        let strength = cpp_class_declaration_strength(analyzer, &candidate);
17491        (candidate, strength)
17492    }))
17493}
17494
17495pub fn collapse_owner_candidates(
17496    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
17497) -> DirectOwnerResolution {
17498    let mut full_definition = None;
17499    let mut forwards = Vec::new();
17500    for (candidate, strength) in candidates {
17501        match strength {
17502            CppClassDeclarationStrength::Full if full_definition.is_some() => {
17503                return DirectOwnerResolution::Ambiguous;
17504            }
17505            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17506            CppClassDeclarationStrength::Forward => forwards.push(candidate),
17507            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
17508        }
17509    }
17510    if let Some(owner) = full_definition {
17511        DirectOwnerResolution::UniqueFull(owner)
17512    } else if !forwards.is_empty() {
17513        DirectOwnerResolution::ForwardsOnly(forwards)
17514    } else {
17515        DirectOwnerResolution::None
17516    }
17517}
17518
17519#[cfg(any(test, feature = "test-support"))]
17520pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17521    unique_logical_forward_owner(forwards)
17522}
17523
17524fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17525    let first = forwards.pop()?;
17526    forwards
17527        .iter()
17528        .all(|forward| same_logical_symbol(forward, &first))
17529        .then_some(first)
17530}
17531
17532pub fn cpp_class_declaration_strength(
17533    analyzer: &CppGraphSource<'_>,
17534    candidate: &CodeUnit,
17535) -> CppClassDeclarationStrength {
17536    // The answer is a pure function of the unit's ranges and its file's tree,
17537    // and the inverse scan asks it once per declaration seed. On a translation
17538    // unit the parser could not fully recover, each ask re-derives the
17539    // export-macro recovery shapes from the file's `ERROR` subtrees, so without
17540    // this memo one file's scan is quadratic in its own size: 97% of Catch2's
17541    // 284 s inverse scan of `extras/catch_amalgamated.cpp` was in this call
17542    // (#1496).
17543    let Some(cpp) = analyzer.cpp else {
17544        return uncached_cpp_class_declaration_strength(analyzer, candidate);
17545    };
17546    if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
17547        return strength;
17548    }
17549    let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
17550    cpp.cache_class_declaration_strength(candidate, strength);
17551    strength
17552}
17553
17554fn uncached_cpp_class_declaration_strength(
17555    analyzer: &CppGraphSource<'_>,
17556    candidate: &CodeUnit,
17557) -> CppClassDeclarationStrength {
17558    if let Some(cpp) = analyzer.cpp
17559        && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
17560    {
17561        return cpp_class_declaration_strength_in_tree(
17562            analyzer,
17563            &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
17564            candidate,
17565            prepared.source(),
17566            prepared.tree().root_node(),
17567        );
17568    }
17569    let Some(source) = analyzer.indexed_source(candidate.source()) else {
17570        return CppClassDeclarationStrength::Unknown;
17571    };
17572    #[cfg(any(test, feature = "test-support"))]
17573    if let Some(cpp) = analyzer.cpp {
17574        cpp.record_cpp_class_strength_parse_for_test();
17575    }
17576    let mut parser = Parser::new();
17577    if parser
17578        .set_language(&tree_sitter_cpp::LANGUAGE.into())
17579        .is_err()
17580    {
17581        return CppClassDeclarationStrength::Unknown;
17582    }
17583    let Some(tree) = parser.parse(&source, None) else {
17584        return CppClassDeclarationStrength::Unknown;
17585    };
17586    // This branch reparses a file the analyzer has no prepared tree for, so its
17587    // recovery index is that one tree's and cannot be shared.
17588    let recovered_export_classes =
17589        CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
17590    cpp_class_declaration_strength_in_tree(
17591        analyzer,
17592        &recovered_export_classes,
17593        candidate,
17594        &source,
17595        tree.root_node(),
17596    )
17597}
17598
17599fn cpp_class_declaration_strength_in_tree(
17600    analyzer: &CppGraphSource<'_>,
17601    recovered_export_classes: &CppRecoveredExportClassIndex,
17602    candidate: &CodeUnit,
17603    source: &str,
17604    root: Node<'_>,
17605) -> CppClassDeclarationStrength {
17606    let ranges = analyzer.ranges(candidate);
17607    let mut saw_forward = false;
17608    for range in ranges {
17609        // The recovered export-macro shapes answer for their own ranges; only a
17610        // range no recovery claims is read as a plain specifier.
17611        match recovered_class_body_at(
17612            recovered_export_classes,
17613            root,
17614            source,
17615            candidate.identifier(),
17616            &range,
17617        ) {
17618            Some(true) => return CppClassDeclarationStrength::Full,
17619            Some(false) => {
17620                saw_forward = true;
17621                continue;
17622            }
17623            None => {}
17624        }
17625        // Only a node covering the range's start byte can be the specifier for
17626        // this range, so apply that test where nodes enter the stack rather
17627        // than where they leave it. Pushing first meant one ask enqueued every
17628        // sibling at every level it descended, which on a translation unit with
17629        // thousands of top-level declarations is a per-ask cost proportional to
17630        // the file (#1496).
17631        let covers_range_start = |node: &Node<'_>| {
17632            node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
17633        };
17634        let mut stack = Vec::new();
17635        if covers_range_start(&root) {
17636            stack.push(root);
17637        }
17638        while let Some(node) = stack.pop() {
17639            if node.start_byte() == range.start_byte
17640                && node.end_byte() == range.end_byte
17641                && matches!(
17642                    node.kind(),
17643                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
17644                )
17645            {
17646                if cpp_class_node_has_body(node) {
17647                    return CppClassDeclarationStrength::Full;
17648                }
17649                saw_forward = true;
17650            }
17651            let mut cursor = node.walk();
17652            stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
17653        }
17654    }
17655    if saw_forward {
17656        CppClassDeclarationStrength::Forward
17657    } else {
17658        CppClassDeclarationStrength::Unknown
17659    }
17660}
17661
17662fn cpp_class_node_has_body(node: Node<'_>) -> bool {
17663    node.child_by_field_name("body").is_some() || {
17664        let mut cursor = node.walk();
17665        node.named_children(&mut cursor).any(|child| {
17666            matches!(
17667                child.kind(),
17668                "declaration_list" | "field_declaration_list" | "enumerator_list"
17669            )
17670        })
17671    }
17672}
17673
17674#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17675enum CppCTagKind {
17676    Struct,
17677    Union,
17678}
17679
17680fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
17681    let declaration = analyzer.get_source(code_unit, false)?;
17682    let mut parser = Parser::new();
17683    parser
17684        .set_language(&tree_sitter_cpp::LANGUAGE.into())
17685        .ok()?;
17686    let tree = parser.parse(&declaration, None)?;
17687    let mut stack = vec![tree.root_node()];
17688    while let Some(node) = stack.pop() {
17689        let kind = match node.kind() {
17690            "struct_specifier" => CppCTagKind::Struct,
17691            "union_specifier" => CppCTagKind::Union,
17692            _ => {
17693                let mut cursor = node.walk();
17694                stack.extend(node.named_children(&mut cursor));
17695                continue;
17696            }
17697        };
17698        if node
17699            .child_by_field_name("name")
17700            .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
17701        {
17702            return Some(kind);
17703        }
17704        let mut cursor = node.walk();
17705        stack.extend(node.named_children(&mut cursor));
17706    }
17707    None
17708}
17709
17710pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17711    if !code_unit.owner_is_type_scope() {
17712        return None;
17713    }
17714    let owner_fq = code_unit.fq().parent()?;
17715    ctx.analyzer
17716        .workspace_definitions()
17717        .exact(&owner_fq)
17718        .into_iter()
17719        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
17720}
17721
17722pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17723    left.kind() == right.kind()
17724        && left.fq_name() == right.fq_name()
17725        && left.signature() == right.signature()
17726        && left.source() == right.source()
17727}
17728
17729pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17730    same_symbol(left, right) || same_logical_symbol(left, right)
17731}
17732
17733pub fn same_visible_global_field_symbol(
17734    analyzer: &CppGraphSource<'_>,
17735    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
17736    left: &CodeUnit,
17737    right: &CodeUnit,
17738) -> bool {
17739    if same_symbol(left, right) {
17740        return true;
17741    }
17742    if !same_logical_symbol(left, right) {
17743        return false;
17744    }
17745    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
17746        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
17747    {
17748        left.source() == right.source()
17749    } else {
17750        true
17751    }
17752}
17753
17754fn cpp_global_field_has_internal_linkage_cached(
17755    analyzer: &CppGraphSource<'_>,
17756    cache: &mut HashMap<CodeUnit, bool>,
17757    candidate: &CodeUnit,
17758) -> bool {
17759    if let Some(internal) = cache.get(candidate) {
17760        return *internal;
17761    }
17762    #[cfg(any(test, feature = "test-support"))]
17763    note_cpp_global_field_internal_linkage_classification_for_test();
17764    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
17765    cache.insert(candidate.clone(), internal);
17766    internal
17767}
17768
17769#[cfg(any(test, feature = "test-support"))]
17770thread_local! {
17771    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17772}
17773
17774#[cfg(any(test, feature = "test-support"))]
17775fn note_cpp_global_field_internal_linkage_classification_for_test() {
17776    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17777        count.set(count.get() + 1);
17778    });
17779}
17780
17781#[cfg(any(test, feature = "test-support"))]
17782pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
17783    body: impl FnOnce() -> T,
17784) -> (T, usize) {
17785    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17786        count.set(0);
17787        let result = body();
17788        let observed = count.get();
17789        count.set(0);
17790        (result, observed)
17791    })
17792}
17793
17794pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17795    left.kind() == right.kind()
17796        && left.fq_name() == right.fq_name()
17797        && left.signature() == right.signature()
17798}
17799
17800pub fn cpp_global_field_has_internal_linkage(
17801    analyzer: &CppGraphSource<'_>,
17802    candidate: &CodeUnit,
17803) -> bool {
17804    if !candidate.is_field() || candidate.short_name().contains('.') {
17805        return false;
17806    }
17807    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
17808        return false;
17809    };
17810    match local_linkage {
17811        CppFieldLinkage::Internal => true,
17812        CppFieldLinkage::External => false,
17813        CppFieldLinkage::InternalUnlessExternalPeer => {
17814            !cpp_global_field_linkage_peers(analyzer, candidate)
17815                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
17816                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
17817        }
17818    }
17819}
17820
17821fn cpp_global_field_linkage_peers<'a>(
17822    analyzer: &CppGraphSource<'a>,
17823    candidate: &'a CodeUnit,
17824) -> impl Iterator<Item = CodeUnit> + 'a {
17825    let name = candidate.fq().clone();
17826    analyzer
17827        .workspace_definitions()
17828        .exact(&name)
17829        .into_iter()
17830        .filter(move |peer| {
17831            if peer == candidate {
17832                return false;
17833            }
17834            #[cfg(any(test, feature = "test-support"))]
17835            note_cpp_global_field_linkage_peer_inspection_for_test();
17836            same_logical_symbol(peer, candidate)
17837        })
17838}
17839
17840#[cfg(any(test, feature = "test-support"))]
17841thread_local! {
17842    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17843}
17844
17845#[cfg(any(test, feature = "test-support"))]
17846fn note_cpp_global_field_linkage_peer_inspection_for_test() {
17847    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17848        count.set(count.get() + 1);
17849    });
17850}
17851
17852#[cfg(any(test, feature = "test-support"))]
17853pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
17854    body: impl FnOnce() -> T,
17855) -> (T, usize) {
17856    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17857        count.set(0);
17858        let result = body();
17859        let observed = count.get();
17860        count.set(0);
17861        (result, observed)
17862    })
17863}
17864
17865fn cpp_global_field_declaration_linkage(
17866    analyzer: &CppGraphSource<'_>,
17867    candidate: &CodeUnit,
17868) -> Option<CppFieldLinkage> {
17869    if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
17870        return Some(linkage);
17871    }
17872    let cpp = analyzer.cpp?;
17873    if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
17874        return cpp_global_field_declaration_linkage_in_tree(
17875            analyzer,
17876            candidate,
17877            prepared.source(),
17878            prepared.tree().root_node(),
17879        );
17880    }
17881    let source = analyzer.indexed_source(candidate.source())?;
17882    let mut parser = Parser::new();
17883    if parser
17884        .set_language(&tree_sitter_cpp::LANGUAGE.into())
17885        .is_err()
17886    {
17887        return None;
17888    }
17889    let tree = parser.parse(&source, None)?;
17890    cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
17891}
17892
17893fn cpp_global_field_declaration_linkage_in_tree(
17894    analyzer: &CppGraphSource<'_>,
17895    candidate: &CodeUnit,
17896    source: &str,
17897    root: Node<'_>,
17898) -> Option<CppFieldLinkage> {
17899    analyzer.ranges(candidate).iter().find_map(|range| {
17900        node_for_exact_range(root, range)
17901            .and_then(enclosing_cpp_field_declaration)
17902            .map(|declaration| {
17903                // One question about one declaration; see `ParentIndex::unindexed`.
17904                cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
17905            })
17906    })
17907}
17908
17909fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
17910    loop {
17911        if matches!(node.kind(), "declaration" | "field_declaration") {
17912            return Some(node);
17913        }
17914        node = node.parent()?;
17915    }
17916}
17917
17918#[cfg(test)]
17919mod tests {
17920    #[test]
17921    fn issue_3089_statement_formal_at_end_of_replacement() {
17922        let parameters = vec!["handle".to_owned(), "block".to_owned()];
17923        for replacement in [
17924            "do { header_event_t* event; if ((handle)->active) block } while (0)",
17925            "do { header_event_t* event; block } while (0)",
17926        ] {
17927            assert!(
17928                super::VisibilityIndex::parse_macro_replacement_body(replacement, &parameters)
17929                    .is_some(),
17930                "{replacement}"
17931            );
17932        }
17933    }
17934    use super::*;
17935
17936    #[test]
17937    fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
17938        let source = "int size(void) { return sizeof(((Payload))); }\n";
17939        let mut parser = Parser::new();
17940        parser
17941            .set_language(&tree_sitter_cpp::LANGUAGE.into())
17942            .expect("C++ grammar");
17943        let tree = parser.parse(source, None).expect("fixture tree");
17944        let start = source.find("Payload").expect("sizeof operand");
17945        let node = tree
17946            .root_node()
17947            .named_descendant_for_byte_range(start, start + "Payload".len())
17948            .expect("focused operand");
17949        let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
17950        let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
17951
17952        assert_eq!(node.kind(), "identifier");
17953        assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
17954        assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
17955    }
17956
17957    fn parse_cpp(source: &str) -> Tree {
17958        let mut parser = Parser::new();
17959        parser
17960            .set_language(&tree_sitter_cpp::LANGUAGE.into())
17961            .expect("C++ grammar");
17962        parser.parse(source, None).expect("fixture tree")
17963    }
17964
17965    fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
17966        let start = source.find(needle).expect("fixture needle");
17967        tree.root_node()
17968            .named_descendant_for_byte_range(start, start + needle.len())
17969            .expect("node at needle")
17970    }
17971
17972    fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
17973        let mut parser = Parser::new();
17974        parser
17975            .set_language(&tree_sitter_cpp::LANGUAGE.into())
17976            .expect("C++ grammar");
17977        let tree = parser.parse(source, None).expect("fixture tree");
17978        PreparedSyntaxTree::new(
17979            PreparedSyntaxSource::Exact(Arc::from(source)),
17980            tree,
17981            compute_line_starts(source),
17982            LanguageDialect::Standard(Language::Cpp),
17983            PreparedSourceOrigin::Disk,
17984            None,
17985        )
17986    }
17987
17988    fn unresolved_include_before(source: &str, reference: &str) -> bool {
17989        let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
17990        let prepared = prepared_cpp(source);
17991        let facts = collect_structured_include_facts(&prepared);
17992        let include_targets = IncludeTargetIndex::build([&file]);
17993        has_unresolved_include_visible_before_in_prepared(
17994            &file,
17995            &prepared,
17996            &include_targets,
17997            &facts,
17998            source.find(reference).expect("reference fixture"),
17999        )
18000    }
18001
18002    #[test]
18003    fn unresolved_include_before_reference_is_visible() {
18004        let source = "#include \"missing.h\"\nint use = Missing;\n";
18005        assert!(unresolved_include_before(source, "Missing"));
18006    }
18007
18008    #[test]
18009    fn unresolved_include_after_reference_is_not_visible() {
18010        let source = "int use = Missing;\n#include \"missing.h\"\n";
18011        assert!(!unresolved_include_before(source, "Missing"));
18012    }
18013
18014    #[test]
18015    fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
18016        let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
18017        assert!(!unresolved_include_before(source, "Missing"));
18018    }
18019
18020    #[test]
18021    fn unresolved_include_in_current_branch_is_visible() {
18022        let source =
18023            "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
18024        assert!(unresolved_include_before(source, "Missing"));
18025    }
18026
18027    /// Two macro-decorated class heads make tree-sitter close `detail` at the
18028    /// first class's `}`, `matchers` at the second's, and `app` at `detail`'s
18029    /// real `}`; the tail parses at translation-unit level and the two real
18030    /// closes for `matchers` and `app` land in a trailing ERROR (#1537).
18031    const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
18032namespace matchers {
18033    namespace detail {
18034        class API [[nodiscard]] First {
18035        public:
18036            int value() const { return count_ + 1; }
18037        private:
18038            int count_;
18039        };
18040        class API [[nodiscard]] Second {
18041        public:
18042            int value() const { return count_ + 2; }
18043        private:
18044            int count_;
18045        };
18046    } // namespace detail
18047
18048    template <typename T>
18049    void tail_function(MatcherBase<T> const& value);
18050
18051    class TailClass {};
18052} // namespace matchers
18053} // namespace app
18054
18055struct AfterAll {};
18056"#;
18057
18058    #[test]
18059    fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
18060        let source = STOLEN_BRACE_CASCADE;
18061        let tree = parse_cpp(source);
18062        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18063
18064        let tail_class = named_node_at(&tree, source, "TailClass");
18065        assert!(
18066            !has_ancestor_kind(tail_class, "namespace_definition"),
18067            "the fixture must reproduce the recovery: the tail has no namespace ancestor"
18068        );
18069        let displaced = named_node_at(&tree, source, "Second");
18070        assert_eq!(
18071            enclosing_namespace_components(displaced, source),
18072            Some(vec!["app".to_string(), "matchers".to_string()]),
18073            "the fixture must displace the second class out of detail"
18074        );
18075
18076        let components = |needle: &str| {
18077            index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
18078        };
18079        assert_eq!(components("First"), ["app", "matchers", "detail"]);
18080        assert_eq!(components("Second"), ["app", "matchers", "detail"]);
18081        assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
18082        assert_eq!(components("tail_function"), ["app", "matchers"]);
18083        assert_eq!(components("TailClass"), ["app", "matchers"]);
18084        assert!(components("AfterAll").is_empty());
18085    }
18086
18087    #[test]
18088    fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
18089        let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
18090        let tree = parse_cpp(clean);
18091        assert!(!tree.root_node().has_error());
18092        assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
18093
18094        // A namespace that merely contains a parse error closes where its
18095        // brace says; the declarations after it keep their parsed scope.
18096        let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
18097        let tree = parse_cpp(damaged);
18098        assert!(tree.root_node().has_error());
18099        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
18100        assert_eq!(
18101            index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
18102            ["a"]
18103        );
18104    }
18105
18106    /// A function-like `#define` in a class body, whose replacement returns a
18107    /// SFINAE type, makes tree-sitter give up on the whole enclosing namespace:
18108    /// `namespace app {` becomes an `ERROR` whose own `namespace`, name and `{`
18109    /// tokens are the only trace of the head, and every declaration the
18110    /// namespace holds becomes their flat sibling. This is Catch2's
18111    /// `catch_decomposer.hpp`, reduced (issue #3084).
18112    const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
18113
18114    class Target {
18115        int value_;
18116    };
18117
18118    template <typename T>
18119    class Holder {
18120    public:
18121        explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
18122
18123#define HOLDER_DEFINE_OP( id, op )                                             \
18124    template <typename U>                                                      \
18125    constexpr friend auto operator op( Holder&& lhs, U&& rhs )                 \
18126        -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> {       \
18127        return Target{};                                                       \
18128    }
18129
18130        HOLDER_DEFINE_OP( equal, == )
18131#undef HOLDER_DEFINE_OP
18132        T m_lhs;
18133    };
18134
18135    class Tail {};
18136}
18137"#;
18138
18139    #[test]
18140    fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
18141        let source = COLLAPSED_NAMESPACE_HEAD;
18142        let tree = parse_cpp(source);
18143        let target = named_node_at(&tree, source, "class Target");
18144
18145        assert!(
18146            !has_ancestor_kind(target, "namespace_definition"),
18147            "the fixture must reproduce the collapse: the class has no namespace ancestor"
18148        );
18149        let head = target.parent().expect("the collapsed namespace envelope");
18150        assert_eq!(
18151            head.kind(),
18152            "ERROR",
18153            "the fixture must keep the namespace head in an ERROR node"
18154        );
18155
18156        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18157        assert_eq!(
18158            index.enclosing_namespace_components(target, source),
18159            ["app"]
18160        );
18161    }
18162
18163    #[test]
18164    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
18165        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
18166        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
18167        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
18168        assert!(indexed_namespace_path_is_recoverable(
18169            &["cache".to_string()],
18170            &indexed,
18171            1,
18172        ));
18173    }
18174
18175    #[test]
18176    fn sort_lookup_units_totally_orders_every_identity_field() {
18177        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
18178        let base = CodeUnit::with_signature(
18179            file.clone(),
18180            CodeUnitType::Function,
18181            "scope",
18182            "value",
18183            Some("()".to_string()),
18184            false,
18185        );
18186        let different_kind = CodeUnit::with_signature(
18187            file.clone(),
18188            CodeUnitType::Field,
18189            "scope",
18190            "value",
18191            Some("()".to_string()),
18192            false,
18193        );
18194        let synthetic = base.with_synthetic(true);
18195
18196        let interner = segment_interner();
18197        let mut member_fq = FqName::new();
18198        member_fq.push(interner.intern("scope", SegmentKind::Package));
18199        member_fq.push(interner.intern("value", SegmentKind::Member));
18200        let different_package_boundary = CodeUnit::from_fq(
18201            file.clone(),
18202            CodeUnitType::Function,
18203            member_fq,
18204            0,
18205            Some("()".to_string()),
18206            false,
18207        );
18208
18209        let mut unknown_fq = FqName::new();
18210        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
18211        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
18212        let different_segment_kind = CodeUnit::from_fq(
18213            file,
18214            CodeUnitType::Function,
18215            unknown_fq,
18216            1,
18217            Some("()".to_string()),
18218            false,
18219        );
18220
18221        let input = vec![
18222            base,
18223            different_kind,
18224            synthetic,
18225            different_package_boundary,
18226            different_segment_kind,
18227        ];
18228        let mut expected = input.clone();
18229        sort_lookup_units(&mut expected);
18230        assert!(expected.windows(2).all(|pair| {
18231            let mut ordered = pair.to_vec();
18232            sort_lookup_units(&mut ordered);
18233            ordered == pair && pair[0] != pair[1]
18234        }));
18235
18236        let mut reversed = input.clone();
18237        reversed.reverse();
18238        sort_lookup_units(&mut reversed);
18239        assert_eq!(reversed, expected);
18240
18241        let mut rotated = input;
18242        rotated.rotate_left(2);
18243        sort_lookup_units(&mut rotated);
18244        assert_eq!(rotated, expected);
18245    }
18246
18247    #[test]
18248    fn displaced_preprocessor_terminator_bounds_the_real_guard() {
18249        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";
18250        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
18251        let parse = |source: &str| {
18252            let mut parser = Parser::new();
18253            parser
18254                .set_language(&tree_sitter_cpp::LANGUAGE.into())
18255                .expect("C++ grammar");
18256            parser.parse(source, None).expect("fixture tree")
18257        };
18258
18259        let tree = parse(damaged);
18260        let root = tree.root_node();
18261        let target = damaged.find("target").expect("target byte");
18262        let declaration = root
18263            .descendant_for_byte_range(target, target + "target".len())
18264            .and_then(|mut node| {
18265                loop {
18266                    if node.kind() == "declaration" {
18267                        break Some(node);
18268                    }
18269                    node = node.parent()?;
18270                }
18271            })
18272            .expect("declaration after the displaced terminator");
18273        let conditional = declaration
18274            .parent()
18275            .filter(|node| node.kind() == "preproc_ifdef")
18276            .expect("damaged inner conditional");
18277        let outer = conditional
18278            .parent()
18279            .filter(|node| node.kind() == "preproc_ifdef")
18280            .expect("ordinary outer include guard");
18281        let terminator = cpp_displaced_preprocessor_terminator(conditional)
18282            .expect("structured displaced #endif");
18283        assert_eq!(node_text(terminator, damaged), "#endif");
18284        assert!(terminator.end_byte() <= declaration.start_byte());
18285        assert!(!preprocessor_conditional_contains_descendant(
18286            conditional,
18287            declaration
18288        ));
18289        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
18290        assert!(preprocessor_conditional_contains_descendant(
18291            outer,
18292            declaration
18293        ));
18294
18295        let tree = parse(guarded);
18296        let conditional = tree
18297            .root_node()
18298            .named_child(0)
18299            .filter(|node| node.kind() == "preproc_ifdef")
18300            .expect("ordinary conditional");
18301        let declaration = conditional
18302            .named_children(&mut conditional.walk())
18303            .find(|node| node.kind() == "declaration")
18304            .expect("guarded declaration");
18305        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18306        assert!(preprocessor_conditional_contains_descendant(
18307            conditional,
18308            declaration
18309        ));
18310
18311        let damaged_alternative = format!(
18312            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
18313            "UNUSED(value)\n".repeat(64)
18314        );
18315        let tree = parse(&damaged_alternative);
18316        let conditional = tree
18317            .root_node()
18318            .named_child(0)
18319            .filter(|node| node.kind() == "preproc_ifdef")
18320            .expect("outer conditional with an alternative");
18321        assert!(conditional.has_error());
18322        assert!(conditional.child_by_field_name("alternative").is_some());
18323        assert!(
18324            conditional
18325                .child(conditional.child_count() - 1)
18326                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
18327        );
18328        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18329
18330        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";
18331        let tree = parse(split_declaration);
18332        let root = tree.root_node();
18333        let conditional = root
18334            .named_children(&mut root.walk())
18335            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
18336            .expect("split declaration conditional");
18337        let target = split_declaration
18338            .find("static int target")
18339            .expect("target byte");
18340        let boundary =
18341            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
18342        assert!(boundary.end_byte <= target, "{boundary:?}");
18343        assert_eq!(boundary.end_line, 9, "{boundary:?}");
18344        let target_node = root
18345            .descendant_for_byte_range(target, target + "static".len())
18346            .expect("target node");
18347        assert!(!preprocessor_conditional_contains_descendant(
18348            conditional,
18349            target_node
18350        ));
18351    }
18352
18353    #[test]
18354    fn fragmented_reference_guard_is_recovered() {
18355        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";
18356        let mut parser = Parser::new();
18357        parser
18358            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18359            .expect("C++ grammar");
18360        let tree = parser.parse(source, None).expect("fixture tree");
18361        let start = source.rfind("helper").expect("reference byte");
18362        let node = tree
18363            .root_node()
18364            .descendant_for_byte_range(start, start + "helper".len())
18365            .expect("reference node");
18366        let mut expected = HashSet::default();
18367        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
18368            vec![
18369                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
18370                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
18371            ],
18372        )));
18373        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
18374    }
18375
18376    #[test]
18377    fn expression_defined_and_ifndef_guards_are_incompatible() {
18378        let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
18379        let mut parser = Parser::new();
18380        parser
18381            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18382            .expect("C++ grammar");
18383        let tree = parser.parse(source, None).expect("fixture tree");
18384        let root = tree.root_node();
18385        let selected_start = source.find("selected").expect("selected declaration");
18386        let rejected_start = source.find("rejected").expect("rejected declaration");
18387        let selected = root
18388            .descendant_for_byte_range(selected_start, selected_start + "selected".len())
18389            .expect("selected node");
18390        let rejected = root
18391            .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
18392            .expect("rejected node");
18393        let selected_guards =
18394            preprocessor_guard_environment(selected, source).expect("selected guards");
18395        let rejected_guards =
18396            preprocessor_guard_environment(rejected, source).expect("rejected guards");
18397
18398        assert!(
18399            merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
18400            "opposite spellings of one macro guard must contradict"
18401        );
18402    }
18403
18404    #[test]
18405    fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
18406        let source = r#"#ifdef _WIN32
18407#if defined(__cplusplus)
18408extern "C"
18409#endif
18410int platform_api(void);
18411#endif
18412
18413#ifdef _WIN32
18414static int entropy_target(void) { return 0; }
18415#else
18416#ifdef HAVE_COMMON_RANDOM
18417static int other_target(void) { return 0; }
18418#elif defined(HAVE_GETENTROPY)
18419static int entropy_target(void) { return 1; }
18420static int use_entropy(void) { return entropy_target(); }
18421#endif
18422#endif
18423"#;
18424        let mut parser = Parser::new();
18425        parser
18426            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18427            .expect("C++ grammar");
18428        let tree = parser.parse(source, None).expect("fixture tree");
18429        let start = source.rfind("entropy_target()").expect("reference");
18430        let node = tree
18431            .root_node()
18432            .descendant_for_byte_range(start, start + "entropy_target".len())
18433            .expect("reference node");
18434        let guards = preprocessor_guard_environment(node, source).expect("active C branch");
18435        assert!(
18436            guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
18437            "{guards:#?}"
18438        );
18439        assert!(
18440            guards.contains(&PreprocessorGuard::Undefined(
18441                "HAVE_COMMON_RANDOM".to_string()
18442            )),
18443            "{guards:#?}"
18444        );
18445        assert!(
18446            guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
18447            "{guards:#?}"
18448        );
18449        assert!(
18450            !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
18451            "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
18452        );
18453    }
18454
18455    #[test]
18456    fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
18457        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";
18458        let mut parser = Parser::new();
18459        parser
18460            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18461            .expect("C++ grammar");
18462        let tree = parser.parse(source, None).expect("fixture tree");
18463        let root = tree.root_node();
18464        let node_at = |text: &str, start: usize| {
18465            root.descendant_for_byte_range(start, start + text.len())
18466                .expect("token node")
18467        };
18468
18469        let key_start = source.find("case KEY").expect("case label") + "case ".len();
18470        let guard_start = source.find("ENABLE_KEYS").expect("guard name");
18471        assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
18472        assert!(!is_ordinary_macro_reference_node(node_at(
18473            "ENABLE_KEYS",
18474            guard_start,
18475        )));
18476    }
18477
18478    #[test]
18479    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
18480        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";
18481        let mut parser = Parser::new();
18482        parser
18483            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18484            .expect("C++ grammar");
18485        let tree = parser.parse(source, None).expect("fixture tree");
18486        let root = tree.root_node();
18487        let definition_start = source.find("target(void)").expect("definition");
18488        let reference_start = source.rfind("target()").expect("reference");
18489        let definition = root
18490            .descendant_for_byte_range(definition_start, definition_start + "target".len())
18491            .expect("definition node");
18492        let reference = root
18493            .descendant_for_byte_range(reference_start, reference_start + "target".len())
18494            .expect("reference node");
18495        let required =
18496            preprocessor_guard_environment(definition, source).expect("definition guard");
18497        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
18498        assert!(guard_requirements_hold_at_reference(
18499            &required,
18500            Some(&active)
18501        ));
18502    }
18503
18504    #[test]
18505    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
18506        let source = "g_autoptr(FuChunkArray) self = make_array();";
18507        let mut parser = Parser::new();
18508        parser
18509            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18510            .expect("C++ grammar");
18511        let tree = parser.parse(source, None).expect("fixture tree");
18512        let statement = tree.root_node().named_child(0).expect("statement");
18513        let binding =
18514            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
18515        assert_eq!(binding.name, "self");
18516        assert_eq!(binding.type_name, "FuChunkArray");
18517        assert_eq!(binding.pointer_depth, 1);
18518
18519        let near_miss = "holder(FuChunkArray) self = make_array();";
18520        let tree = parser.parse(near_miss, None).expect("near-miss tree");
18521        let statement = tree.root_node().named_child(0).expect("statement");
18522        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
18523    }
18524
18525    #[test]
18526    fn boolean_guard_normalization_proves_equivalence_and_implication() {
18527        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
18528        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
18529        let negated_windows_branch =
18530            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
18531        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
18532        assert_eq!(negated_windows_branch, portable);
18533
18534        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
18535        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
18536        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
18537        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
18538        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
18539        assert!(fallback_branch.implies(&fallback_declaration));
18540        assert!(
18541            BooleanGuardExpression::Truthy("FEATURE".to_string())
18542                .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
18543        );
18544        assert!(
18545            BooleanGuardExpression::Undefined("FEATURE".to_string())
18546                .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
18547        );
18548        assert!(
18549            !BooleanGuardExpression::Defined("FEATURE".to_string())
18550                .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
18551        );
18552        assert!(!fallback_declaration.implies(&fallback_branch));
18553    }
18554
18555    #[test]
18556    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
18557        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";
18558        let mut parser = Parser::new();
18559        parser
18560            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18561            .expect("C++ grammar");
18562        let tree = parser.parse(source, None).expect("fixture tree");
18563        let root = tree.root_node();
18564        let call = |marker: &str| {
18565            let start = source.find(marker).expect("call marker");
18566            let mut node = root
18567                .descendant_for_byte_range(start, start + "helper".len())
18568                .expect("call name node");
18569            loop {
18570                if node.kind() == "call_expression" {
18571                    break node;
18572                }
18573                node = node.parent().expect("call expression ancestor");
18574            }
18575        };
18576        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
18577        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
18578        let keyword_call = call("helper(NULL, template); /* bound */");
18579        let keyword_arguments = keyword_call
18580            .child_by_field_name("arguments")
18581            .expect("keyword argument list");
18582        assert_eq!(
18583            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
18584            1
18585        );
18586        assert_eq!(
18587            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
18588            0
18589        );
18590
18591        let unbound_call = call("helper(NULL, template); /* unbound */");
18592        let unbound_arguments = unbound_call
18593            .child_by_field_name("arguments")
18594            .expect("unbound argument list");
18595        assert_eq!(
18596            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
18597            0
18598        );
18599    }
18600
18601    #[test]
18602    fn c_function_declarator_recovery_accepts_invocations_not_binders() {
18603        let source = r#"#define MAKE(type) type *value
18604MAKE(int *);
18605typedef struct Item Item;
18606struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
18607void gen_op_movl(void *s, int first, int second) { }
18608const char *strZ(const char *value) { return value; }
18609int body(void *s) {
18610    MAKE(int *);
18611    gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
18612                offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
18613    execvp(strZ(value), UNCONSTIFY(char **, args));
18614}
18615 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
18616    if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
18617int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
18618    if (d->units) {
18619        switch (target->nodetype) {
18620        case 1:
18621        case 2:
18622            break;
18623        default:
18624            AMEND_WRONG_NODETYPE("deviation", "replace", "units");
18625        }
18626        DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
18627        lysdict_remove(ctx, ((struct Item *)target)->units);
18628        DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
18629    }
18630     return 0;
18631 }
18632STATIC EFI_STATUS Encode () { return 0; }
18633"#;
18634        let tree = parse_cpp(source);
18635        let top_macro_start = source.find("MAKE(int *);").expect("top macro");
18636        let top_macro = tree
18637            .root_node()
18638            .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
18639            .expect("top macro node");
18640        let body_macro_start = source
18641            .match_indices("MAKE(int *);")
18642            .nth(1)
18643            .expect("body macro")
18644            .0;
18645        let body_macro = tree
18646            .root_node()
18647            .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
18648            .expect("body macro node");
18649        let function_call_start = source
18650            .find("gen_op_movl(s, offsetof(CPUX86State")
18651            .expect("function call");
18652        let function_call = tree
18653            .root_node()
18654            .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
18655            .expect("function call node");
18656        let strz_start = source.find("strZ(value)").expect("nested function call");
18657        let strz = tree
18658            .root_node()
18659            .named_descendant_for_byte_range(strz_start, strz_start + 4)
18660            .expect("nested function call node");
18661        let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
18662        let recovered_call = tree
18663            .root_node()
18664            .named_descendant_for_byte_range(
18665                recovered_call_start,
18666                recovered_call_start + "lysdict_remove".len(),
18667            )
18668            .expect("recovered call node");
18669        let binder_start = source.find("Encode").expect("binder");
18670        let binder = tree
18671            .root_node()
18672            .named_descendant_for_byte_range(binder_start, binder_start + 6)
18673            .expect("binder node");
18674
18675        assert!(recovered_c_function_declarator_invocation(top_macro));
18676        assert!(recovered_c_function_declarator_invocation(body_macro));
18677        assert!(recovered_c_function_declarator_invocation(function_call));
18678        assert!(recovered_c_function_declarator_invocation(strz));
18679        assert!(recovered_c_function_declarator_invocation(recovered_call));
18680        assert!(!recovered_c_function_declarator_invocation(binder));
18681    }
18682
18683    #[test]
18684    fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
18685        let source = r#"typedef int krb5_context;
18686int helper(int first, int second) { return first + second; }
18687static krb5_context ctx;
18688int main(int argc, char **argv) {
18689    int ccinitial;
18690    const char *collection_name, *typename;
18691    typename = helper(ctx, ccinitial);
18692    return 0;
18693}
18694"#;
18695        let tree = parse_cpp(source);
18696        let ctx = tree
18697            .root_node()
18698            .descendant_for_byte_range(
18699                source.find("ctx, ccinitial").expect("ctx argument"),
18700                source.find("ctx, ccinitial").expect("ctx argument") + 3,
18701            )
18702            .expect("ctx node");
18703        let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
18704        let ccinitial = tree
18705            .root_node()
18706            .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
18707            .expect("sibling node");
18708        let typename = named_node_at(&tree, source, "typename = helper");
18709        let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
18710
18711        assert_eq!(ctx.kind(), "identifier");
18712        assert!(recovered_c_parenthesized_declarator_reference(ctx));
18713        assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
18714        assert!(!recovered_c_parenthesized_declarator_reference(typename));
18715        assert!(!recovered_c_parenthesized_declarator_reference(helper));
18716    }
18717
18718    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
18719        let mut parser = Parser::new();
18720        parser
18721            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18722            .expect("C++ grammar");
18723        let tree = parser.parse(source, None).expect("C++ fixture tree");
18724        let mut stack = vec![tree.root_node()];
18725        while let Some(node) = stack.pop() {
18726            if node.kind() == "enum_specifier" {
18727                return flattened_macro_namespace_components(node, source);
18728            }
18729            let mut cursor = node.walk();
18730            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
18731            stack.extend(children.into_iter().rev());
18732        }
18733        None
18734    }
18735
18736    #[test]
18737    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
18738        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18739namespace detail
18740{
18741enum class value_t { null };
18742}
18743NLOHMANN_JSON_NAMESPACE_END
18744NLOHMANN_JSON_NAMESPACE_BEGIN
18745namespace next
18746{
18747struct next_type {};
18748}
18749NLOHMANN_JSON_NAMESPACE_END
18750"#;
18751        assert_eq!(
18752            first_enum_flattened_namespace(complete),
18753            Some(vec!["detail".to_string()])
18754        );
18755
18756        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
18757        assert_eq!(
18758            first_enum_flattened_namespace(&stale_end),
18759            Some(vec!["detail".to_string()]),
18760            "a stale end marker before the begin marker must not replace the intended namespace"
18761        );
18762
18763        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18764namespace detail
18765{
18766enum class value_t { null };
18767}
18768struct next_type {};
18769"#;
18770        assert_eq!(first_enum_flattened_namespace(incomplete), None);
18771    }
18772}
18773
18774/// Comparator laws for the total C++ lookup order introduced by #1876.
18775///
18776/// `sort_lookup_units` is the single tie-break the C++ resolver applies before
18777/// any "first wins" selection (template families in #1836, the visible
18778/// identifier index, the type-candidate lists). If its comparator is not a
18779/// total order over CodeUnit identity, some pair stays tied and the survivor
18780/// falls back to the order the units arrived in -- which is FxHash iteration
18781/// order over keys whose hash covers the absolute workspace root. That is the
18782/// exact mechanism behind #1836 and the #414 / #432 heisenbug, so the laws are
18783/// checked generatively rather than on one hand-picked list.
18784///
18785/// CodeUnit identity is `source`, `kind`, `fq`, `package_segment_count`,
18786/// `signature` and `synthetic` (see `impl PartialEq for CodeUnit`); a CodeUnit
18787/// carries no range, so declaration ranges are covered by the workspace-level
18788/// property in `tests/suite_analyzers/determinism_properties.rs` instead.
18789#[cfg(test)]
18790mod lookup_order_properties {
18791    use super::*;
18792    use proptest::prelude::*;
18793
18794    /// Segment spellings the C++ extractor and the shared renderer actually
18795    /// produce, including the `$`-joined nested spellings and non-ASCII
18796    /// identifiers that a byte-wise comparison has to keep apart.
18797    const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
18798    const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
18799    /// Two roots so the order is pinned across workspaces as well as inside
18800    /// one: the root path is precisely the byte string that used to leak into
18801    /// iteration order.
18802    const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
18803    const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
18804    const KINDS: [CodeUnitType; 6] = [
18805        CodeUnitType::Class,
18806        CodeUnitType::Function,
18807        CodeUnitType::Field,
18808        CodeUnitType::Module,
18809        CodeUnitType::Macro,
18810        CodeUnitType::FileScope,
18811    ];
18812
18813    /// Where one unit sits relative to another under the comparator that
18814    /// `sort_lookup_units` owns.
18815    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
18816    enum ProbedOrder {
18817        Before,
18818        Tied,
18819        After,
18820        /// Both directions reported "strictly first": the comparator is not
18821        /// dual, and no sort over it can be order-independent.
18822        Contradictory,
18823    }
18824
18825    impl ProbedOrder {
18826        fn mirror(self) -> Self {
18827            match self {
18828                ProbedOrder::Before => ProbedOrder::After,
18829                ProbedOrder::After => ProbedOrder::Before,
18830                other => other,
18831            }
18832        }
18833
18834        /// -1 / 0 / +1, so transitivity reads as the `<= 0` law.
18835        fn signum(self) -> i8 {
18836            match self {
18837                ProbedOrder::Before => -1,
18838                ProbedOrder::Tied => 0,
18839                ProbedOrder::After => 1,
18840                ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
18841            }
18842        }
18843    }
18844
18845    /// Read the comparator through its only caller.
18846    ///
18847    /// `sort_lookup_units` is a stable sort, so for a two-element slice the
18848    /// output says exactly whether the comparator put the second element
18849    /// strictly first. Sorting both arrangements of one pair therefore reports
18850    /// the comparator's verdict in both directions, including the contradictory
18851    /// case a single sort would hide.
18852    fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
18853        if left == right {
18854            // A stable sort cannot distinguish two equal values, and `Equal` is
18855            // the only verdict a total order can give them.
18856            return ProbedOrder::Tied;
18857        }
18858        let mut forward = vec![left.clone(), right.clone()];
18859        sort_lookup_units(&mut forward);
18860        let mut backward = vec![right.clone(), left.clone()];
18861        sort_lookup_units(&mut backward);
18862        let left_first = backward[0] == *left;
18863        let right_first = forward[0] == *right;
18864        match (left_first, right_first) {
18865            (true, true) => ProbedOrder::Contradictory,
18866            (true, false) => ProbedOrder::Before,
18867            (false, true) => ProbedOrder::After,
18868            (false, false) => ProbedOrder::Tied,
18869        }
18870    }
18871
18872    /// `(kind, text)` per segment. `CodeUnit`'s own `Debug` prints interned
18873    /// segment IDs, which are process-local and say nothing about a failure.
18874    fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
18875        let interner = segment_interner();
18876        unit.fq()
18877            .segments()
18878            .iter()
18879            .map(|&id| {
18880                let (text, kind) = interner.resolve(id);
18881                (kind.name(), text)
18882            })
18883            .collect()
18884    }
18885
18886    fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
18887        (
18888            0..ROOT_NAMES.len(),
18889            0..REL_PATHS.len(),
18890            0..KINDS.len(),
18891            prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
18892            0..3usize,
18893            0..SIGNATURES.len(),
18894            any::<bool>(),
18895        )
18896            .prop_map(
18897                |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
18898                    let source = ProjectFile::new(
18899                        std::env::temp_dir().join(ROOT_NAMES[root]),
18900                        REL_PATHS[rel_path],
18901                    );
18902                    let interner = segment_interner();
18903                    let mut fq = FqName::new();
18904                    for (atom, segment_kind) in &segments {
18905                        fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
18906                    }
18907                    // `from_fq` requires a non-empty declaration tail.
18908                    let package_segment_count = package_prefix % fq.len();
18909                    CodeUnit::from_fq(
18910                        source,
18911                        KINDS[kind],
18912                        fq,
18913                        package_segment_count,
18914                        SIGNATURES[signature].map(str::to_string),
18915                        synthetic,
18916                    )
18917                },
18918            )
18919    }
18920
18921    proptest! {
18922        #![proptest_config(ProptestConfig::with_cases(256))]
18923
18924        /// Reflexivity and duality: a unit ties with itself, and no pair is
18925        /// strictly first in both directions.
18926        #[test]
18927        fn lookup_order_is_reflexive_and_dual(
18928            left in code_unit_strategy(),
18929            right in code_unit_strategy(),
18930        ) {
18931            prop_assert_eq!(
18932                probe_order(&left, &left),
18933                ProbedOrder::Tied,
18934                "a unit must tie with itself: {:?}",
18935                left
18936            );
18937            let forward = probe_order(&left, &right);
18938            prop_assert_ne!(
18939                forward,
18940                ProbedOrder::Contradictory,
18941                "comparator put each of these strictly first: left={:?} right={:?}",
18942                left,
18943                right
18944            );
18945            prop_assert_eq!(
18946                probe_order(&right, &left),
18947                forward.mirror(),
18948                "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
18949                left,
18950                right
18951            );
18952        }
18953
18954        /// Transitivity: `a <= b` and `b <= c` imply `a <= c`.
18955        #[test]
18956        fn lookup_order_is_transitive(
18957            a in code_unit_strategy(),
18958            b in code_unit_strategy(),
18959            c in code_unit_strategy(),
18960        ) {
18961            let ab = probe_order(&a, &b);
18962            let bc = probe_order(&b, &c);
18963            let ac = probe_order(&a, &c);
18964            for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
18965                prop_assert_ne!(
18966                    probed,
18967                    ProbedOrder::Contradictory,
18968                    "comparator is not dual over {}: a={:?} b={:?} c={:?}",
18969                    pair,
18970                    a,
18971                    b,
18972                    c
18973                );
18974            }
18975            if ab.signum() <= 0 && bc.signum() <= 0 {
18976                prop_assert!(
18977                    ac.signum() <= 0,
18978                    "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
18979                     a={:?} b={:?} c={:?}",
18980                    ab,
18981                    bc,
18982                    ac,
18983                    a,
18984                    b,
18985                    c
18986                );
18987            }
18988        }
18989
18990        /// The property #1876 exists for: only identical identities may tie.
18991        /// A tie between distinct units is the residual hash-order dependence.
18992        #[test]
18993        fn lookup_order_separates_distinct_identities(
18994            left in code_unit_strategy(),
18995            right in code_unit_strategy(),
18996        ) {
18997            if probe_order(&left, &right) == ProbedOrder::Tied {
18998                prop_assert_eq!(
18999                    &left,
19000                    &right,
19001                    "distinct identities tied, so their order is whatever order they \
19002                     arrived in: left_segments={:?} right_segments={:?}",
19003                    fq_segments(&left),
19004                    fq_segments(&right)
19005                );
19006            }
19007        }
19008
19009        /// The consequence the resolver relies on: the sorted list is a
19010        /// function of the SET of units, not of the order they were pushed in.
19011        #[test]
19012        fn lookup_sort_is_permutation_invariant(
19013            units in prop::collection::vec(code_unit_strategy(), 1..=8),
19014        ) {
19015            let mut sorted = units.clone();
19016            sort_lookup_units(&mut sorted);
19017            for rotation in 0..units.len() {
19018                for reversed in [false, true] {
19019                    let mut permuted = units.clone();
19020                    permuted.rotate_left(rotation);
19021                    if reversed {
19022                        permuted.reverse();
19023                    }
19024                    sort_lookup_units(&mut permuted);
19025                    prop_assert_eq!(
19026                        &permuted,
19027                        &sorted,
19028                        "sorting a permutation gave a different list \
19029                         (rotation={}, reversed={}): input={:?}",
19030                        rotation,
19031                        reversed,
19032                        units
19033                    );
19034                }
19035            }
19036        }
19037    }
19038}