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}
63
64#[derive(Clone, Copy, PartialEq, Eq)]
65pub enum TargetKind {
66    Type,
67    Constructor,
68    FreeFunction,
69    Method,
70    GlobalField,
71    MemberField,
72    Macro,
73}
74
75pub enum LexicalTypeResolution {
76    Resolved {
77        unit: CodeUnit,
78        components: Vec<String>,
79        candidates: Vec<CodeUnit>,
80    },
81    Ambiguous,
82    Missing,
83}
84
85#[derive(Clone, Copy)]
86enum TypeCandidateResolution<'a> {
87    Canonical,
88    PreserveAlias,
89    PreserveTarget(&'a CodeUnit),
90}
91
92/// Why a name did not reduce to one indexed type declaration.
93///
94/// The two answers are not interchangeable. `Ambiguous` means the index holds
95/// several declarations and the caller must choose; `Unresolvable` means the
96/// index holds none, which is a boundary the workspace cannot see past. A
97/// `using`/`typedef` alias to a template parameter or to a standard-library
98/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
99/// answer with an empty candidate list (#1828).
100#[derive(Clone, Copy, Debug, PartialEq, Eq)]
101enum TypeCandidateFailure {
102    Ambiguous,
103    Unresolvable,
104}
105
106impl TypeCandidateFailure {
107    fn lexical_resolution(self) -> LexicalTypeResolution {
108        match self {
109            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
110            Self::Unresolvable => LexicalTypeResolution::Missing,
111        }
112    }
113}
114
115pub enum LexicalCallableValueResolution {
116    Type(CodeUnit),
117    FreeFunction(CodeUnit),
118    Ambiguous,
119    Missing,
120}
121
122pub enum UsingEnumMemberResolution {
123    Resolved { owner: CodeUnit, member: CodeUnit },
124    Ambiguous,
125    Missing,
126}
127
128pub enum NamespaceValueResolution {
129    Resolved,
130    Ambiguous,
131    Missing,
132}
133
134#[derive(Clone, Debug, PartialEq, Eq)]
135pub enum OrdinaryMacroReferenceResolution {
136    Resolved(CodeUnit),
137    Ambiguous,
138    Missing,
139}
140
141#[derive(Clone, Debug, PartialEq, Eq)]
142pub enum RecoveredCReferenceRanges {
143    Complete(Vec<Range>),
144    LimitExceeded,
145}
146
147pub fn resolve_namespace_value(
148    analyzer: &CppGraphSource<'_>,
149    visibility: &VisibilityIndex<'_>,
150    file: &ProjectFile,
151    namespace: &str,
152    name: &str,
153    before_byte: usize,
154) -> NamespaceValueResolution {
155    let mut matches = Vec::new();
156    for candidate in visibility.visible_identifier_candidates(file, name) {
157        if type_owner_of(analyzer, candidate).is_some()
158            || candidate.package_name() != namespace
159            || (candidate.source() == file
160                && !analyzer
161                    .ranges(candidate)
162                    .iter()
163                    .any(|range| range.start_byte < before_byte))
164            || matches
165                .iter()
166                .any(|existing| same_visible_symbol(existing, candidate))
167        {
168            continue;
169        }
170        matches.push(candidate.clone());
171        if matches.len() > 1 {
172            return NamespaceValueResolution::Ambiguous;
173        }
174    }
175    matches
176        .pop()
177        .map(|_| NamespaceValueResolution::Resolved)
178        .unwrap_or(NamespaceValueResolution::Missing)
179}
180
181pub(crate) struct ScopedUsingEnumOwners {
182    scopes: Vec<Vec<CodeUnit>>,
183}
184
185/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
186/// Cross-file and inherited class imports are deliberately not inferred without persisted
187/// evidence; a missing imported enumerator therefore remains unproven rather than being
188/// misresolved.
189pub(crate) struct SemanticUsingEnumOwners {
190    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
191    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
192}
193
194pub(crate) enum SemanticUsingEnumMemberResolution {
195    Class(UsingEnumMemberResolution),
196    Namespace(UsingEnumMemberResolution),
197    Missing,
198}
199
200impl SemanticUsingEnumOwners {
201    pub(crate) fn new() -> Self {
202        Self {
203            class_imports: HashMap::default(),
204            namespace_imports: HashMap::default(),
205        }
206    }
207
208    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
209        let imports = self.class_imports.entry(class).or_default();
210        if !imports
211            .iter()
212            .any(|existing| same_visible_symbol(existing, &enum_owner))
213        {
214            imports.push(enum_owner);
215        }
216    }
217
218    pub fn import_namespace(
219        &mut self,
220        namespace: Vec<String>,
221        declaration_byte: usize,
222        enum_owner: CodeUnit,
223    ) {
224        let imports = self.namespace_imports.entry(namespace).or_default();
225        if !imports
226            .iter()
227            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
228        {
229            imports.push((declaration_byte, enum_owner));
230        }
231    }
232
233    pub fn resolve_member(
234        &self,
235        visibility: &VisibilityIndex<'_>,
236        file: &ProjectFile,
237        class: Option<&CodeUnit>,
238        namespace: &[String],
239        before_byte: usize,
240        name: &str,
241    ) -> SemanticUsingEnumMemberResolution {
242        if let Some(class) = class
243            && let Some((_, imports)) = self
244                .class_imports
245                .iter()
246                .find(|(owner, _)| same_visible_symbol(owner, class))
247        {
248            let resolution =
249                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
250            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
251                return SemanticUsingEnumMemberResolution::Class(resolution);
252            }
253        }
254        for prefix_len in (0..=namespace.len()).rev() {
255            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
256                continue;
257            };
258            let owners = imports
259                .iter()
260                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
261                .map(|(_, owner)| owner);
262            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
263            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
264                return SemanticUsingEnumMemberResolution::Namespace(resolution);
265            }
266        }
267        SemanticUsingEnumMemberResolution::Missing
268    }
269}
270
271fn resolve_using_enum_member_for_owners<'a>(
272    visibility: &VisibilityIndex<'_>,
273    file: &ProjectFile,
274    owners: impl IntoIterator<Item = &'a CodeUnit>,
275    name: &str,
276) -> UsingEnumMemberResolution {
277    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
278    for owner in owners {
279        for member in visibility.visible_members_for_owner_name(file, owner, name) {
280            if !member.is_field()
281                || matches.iter().any(|(existing_owner, existing_member)| {
282                    same_visible_symbol(existing_owner, owner)
283                        && same_visible_symbol(existing_member, member)
284                })
285            {
286                continue;
287            }
288            matches.push((owner.clone(), member.clone()));
289        }
290    }
291    match matches.len() {
292        0 => UsingEnumMemberResolution::Missing,
293        1 => {
294            let (owner, member) = matches.pop().expect("one using-enum match");
295            UsingEnumMemberResolution::Resolved { owner, member }
296        }
297        _ => UsingEnumMemberResolution::Ambiguous,
298    }
299}
300
301impl ScopedUsingEnumOwners {
302    pub(crate) fn new() -> Self {
303        Self {
304            scopes: vec![Vec::new()],
305        }
306    }
307
308    pub fn enter_scope(&mut self) {
309        self.scopes.push(Vec::new());
310    }
311
312    pub fn exit_scope(&mut self) {
313        if self.scopes.len() > 1 {
314            self.scopes.pop();
315        }
316    }
317
318    pub fn import(&mut self, owner: CodeUnit) {
319        let scope = self
320            .scopes
321            .last_mut()
322            .expect("using-enum scope stack is never empty");
323        if !scope
324            .iter()
325            .any(|existing| same_visible_symbol(existing, &owner))
326        {
327            scope.push(owner);
328        }
329    }
330
331    pub fn resolve_member(
332        &self,
333        visibility: &VisibilityIndex<'_>,
334        file: &ProjectFile,
335        name: &str,
336    ) -> UsingEnumMemberResolution {
337        for scope in self.scopes.iter().rev() {
338            let resolution =
339                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
340            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
341                return resolution;
342            }
343        }
344        UsingEnumMemberResolution::Missing
345    }
346}
347
348#[derive(Clone)]
349pub struct TargetSpec {
350    pub target: CodeUnit,
351    pub kind: TargetKind,
352    pub owner: Option<CodeUnit>,
353    pub member_name: String,
354    pub callable_arity: Option<CallableArity>,
355    pub activated_callable_arities: Vec<ActivatedCallableArity>,
356    pub param_types: Option<Vec<String>>,
357    pub enum_owner_kind: EnumOwnerKind,
358    pub owner_is_forward_declaration: bool,
359    pub callable_has_definition_body: bool,
360}
361
362#[derive(Clone, Copy)]
363pub struct ActivatedCallableArity {
364    pub activation_byte: usize,
365    pub arity: CallableArity,
366}
367
368#[derive(Debug, PartialEq, Eq, Hash)]
369pub struct TypeScanKey {
370    target: LogicalSymbolKey,
371    member_name: String,
372}
373
374#[derive(Clone, Debug, PartialEq, Eq, Hash)]
375struct LogicalSymbolKey {
376    kind: CodeUnitType,
377    fq_name: String,
378    signature: Option<String>,
379}
380
381struct ResolvedTypeOwner {
382    unit: CodeUnit,
383    is_forward_declaration: bool,
384}
385
386#[derive(Clone, Copy, PartialEq, Eq)]
387pub enum EnumOwnerKind {
388    Scoped,
389    Unscoped,
390    NonEnum,
391}
392
393impl TargetSpec {
394    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
395        (self.kind == TargetKind::Type).then(|| TypeScanKey {
396            target: logical_symbol_key(&self.target),
397            member_name: self.member_name.clone(),
398        })
399    }
400
401    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
402        if target.is_class() {
403            return Some(Self::new(
404                target.clone(),
405                TargetKind::Type,
406                Some(target.clone()),
407                target.identifier().to_string(),
408                None,
409                None,
410            ));
411        }
412
413        if target.is_field() {
414            // A namespace (module) is not a receiver: a namespace-scoped constant such as
415            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
416            // qualified from outside, exactly like a global. Treating a module owner as a
417            // member-field owner makes the receiver/owner-context match reject every valid
418            // reference, so resolve it as a global field instead.
419            let owner = type_owner_of(analyzer, target);
420            let kind = if owner.is_some() {
421                TargetKind::MemberField
422            } else {
423                TargetKind::GlobalField
424            };
425            let enum_owner_kind = owner
426                .as_ref()
427                .map(|owner| classify_enum_owner(analyzer, owner))
428                .unwrap_or(EnumOwnerKind::NonEnum);
429            let mut spec = Self::new(
430                target.clone(),
431                kind,
432                owner,
433                target.identifier().to_string(),
434                None,
435                None,
436            );
437            spec.enum_owner_kind = enum_owner_kind;
438            return Some(spec);
439        }
440
441        if target.is_function() {
442            // Free functions declared inside a namespace have a module owner; that namespace is
443            // not a call receiver, so resolve them as free functions rather than methods.
444            let owner_resolution = target_type_owner_resolution(analyzer, target);
445            let owner_is_forward_declaration = owner_resolution
446                .as_ref()
447                .is_some_and(|owner| owner.is_forward_declaration);
448            let owner = owner_resolution.map(|owner| owner.unit);
449            let kind = if owner.as_ref().is_some_and(|owner| {
450                target.identifier() == owner.identifier()
451                    || analyzer
452                        .cpp
453                        .and_then(|cpp| cpp.template_metadata(owner))
454                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
455            }) {
456                TargetKind::Constructor
457            } else if owner.is_some() {
458                TargetKind::Method
459            } else {
460                TargetKind::FreeFunction
461            };
462            let mut spec = Self::new(
463                target.clone(),
464                kind,
465                owner,
466                target.identifier().to_string(),
467                Some(cpp_callable_arity(analyzer, target)),
468                cpp_callable_parameter_types(analyzer, target),
469            );
470            spec.owner_is_forward_declaration = owner_is_forward_declaration;
471            spec.callable_has_definition_body =
472                callable_target_has_definition_body(analyzer, target);
473            return Some(spec);
474        }
475
476        if target.is_macro() {
477            return Some(Self::new(
478                target.clone(),
479                TargetKind::Macro,
480                None,
481                target.identifier().to_string(),
482                None,
483                None,
484            ));
485        }
486
487        None
488    }
489
490    pub fn with_visible_callable_arities<'a>(
491        &'a self,
492        analyzer: &CppGraphSource<'_>,
493        cpp: &dyn CppSource,
494        visibility: &VisibilityIndex<'_>,
495        file: &ProjectFile,
496        prepared: &PreparedSyntaxTree,
497    ) -> Cow<'a, Self> {
498        let macro_parameter_arity =
499            visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
500        let activated_callable_arities =
501            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
502        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
503            return Cow::Borrowed(self);
504        }
505        let mut effective = self.clone();
506        if let Some(macro_parameter_arity) = macro_parameter_arity {
507            effective.callable_arity = Some(macro_parameter_arity);
508        }
509        effective.activated_callable_arities = activated_callable_arities;
510        Cow::Owned(effective)
511    }
512
513    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
514        let base = self.callable_arity?;
515        Some(
516            self.activated_callable_arities
517                .iter()
518                .filter(|candidate| candidate.activation_byte <= byte)
519                .fold(base, |arity, candidate| {
520                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
521                }),
522        )
523    }
524
525    pub fn new(
526        target: CodeUnit,
527        kind: TargetKind,
528        owner: Option<CodeUnit>,
529        member_name: String,
530        callable_arity: Option<CallableArity>,
531        param_types: Option<Vec<String>>,
532    ) -> Self {
533        Self {
534            target,
535            kind,
536            owner,
537            member_name,
538            callable_arity,
539            activated_callable_arities: Vec::new(),
540            param_types,
541            enum_owner_kind: EnumOwnerKind::NonEnum,
542            owner_is_forward_declaration: false,
543            callable_has_definition_body: false,
544        }
545    }
546}
547
548fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
549    let Some(cpp) = analyzer.cpp else {
550        return false;
551    };
552    let Some(prepared) = cpp.prepared_syntax(analyzer.token, target.source()) else {
553        return false;
554    };
555    analyzer.ranges(target).into_iter().any(|range| {
556        let end = range
557            .start_byte
558            .saturating_add(1)
559            .min(prepared.source().len());
560        let mut current = prepared
561            .tree()
562            .root_node()
563            .descendant_for_byte_range(range.start_byte, end);
564        while let Some(node) = current {
565            match node.kind() {
566                "function_definition" => return true,
567                "declaration" => return false,
568                _ => current = node.parent(),
569            }
570        }
571        false
572    })
573}
574
575fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
576    LogicalSymbolKey {
577        kind: unit.kind(),
578        fq_name: unit.fq_name(),
579        signature: unit.signature().map(str::to_string),
580    }
581}
582
583fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
584    let classify = |source: &str| {
585        let source = source.trim_start();
586        if source.starts_with("enum class ") || source.starts_with("enum struct ") {
587            Some(EnumOwnerKind::Scoped)
588        } else if source.starts_with("enum ") {
589            Some(EnumOwnerKind::Unscoped)
590        } else {
591            None
592        }
593    };
594    owner
595        .signature()
596        .and_then(classify)
597        .or_else(|| {
598            analyzer
599                .get_source(owner, false)
600                .as_deref()
601                .and_then(classify)
602        })
603        .unwrap_or(EnumOwnerKind::NonEnum)
604}
605
606#[derive(Clone, PartialEq, Eq, Hash)]
607pub struct CppScanBinding {
608    pub unit: Option<CodeUnit>,
609    pub type_name: Option<String>,
610    pub indirection: i32,
611}
612
613impl CppScanBinding {
614    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
615        Self {
616            type_name: Some(cpp_name_for(&unit)),
617            unit: Some(unit),
618            indirection,
619        }
620    }
621
622    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
623        Self {
624            type_name: Some(type_name),
625            unit,
626            indirection,
627        }
628    }
629
630    pub fn as_arg_type(&self) -> Option<CppArgType> {
631        let name = self
632            .type_name
633            .clone()
634            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
635        Some(CppArgType {
636            name,
637            unit: self.unit.clone(),
638            indirection: self.indirection,
639            pointee_const: false,
640        })
641    }
642}
643
644type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
645pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
646pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
647type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
648type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
649type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
650type MacroLexicalTemplateCache =
651    HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
652
653type MacroLocalBindingTemplateCache =
654    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
655type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
656type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
657type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
658
659struct StructuredIncludeFact {
660    start_byte: usize,
661    end_byte: usize,
662    path: String,
663}
664
665#[derive(Clone, Default)]
666pub struct MacroEnvironment {
667    bindings: HashMap<String, MacroBinding>,
668    known_undefined_names: HashSet<String>,
669    /// Names the translation unit's compile command proves defined (#2011):
670    /// the `-D`s that survive command ordering, intersected across every
671    /// configuration naming the TU. Seeded once at TU start. An explicit
672    /// `#undef` seen later lands in `known_undefined_names` and wins.
673    build_proven_defines: HashSet<String>,
674    unknown_names: bool,
675    applied_pragma_once_files: HashSet<ProjectFile>,
676    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
677}
678
679/// How many macro events one checkpoint window may cover.
680///
681/// A request for an environment replays only the events between the nearest
682/// earlier checkpoint and its own frontier, so one file's whole scan costs its
683/// event count (the checkpoint build) plus this many applications per request,
684/// whatever order the requests arrive in. The forward cursor this replaced was
685/// optimal for one worker reading one file in byte order and quadratic for the
686/// inverse, which asks many workers for positions that move backwards (#1496).
687pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
688
689/// One event prefix of a file whose environment the index keeps.
690struct MacroEnvironmentCheckpoint {
691    /// How many of the file's events this environment has applied.
692    frontier: usize,
693    environment: Arc<MacroEnvironment>,
694}
695
696/// The replay checkpoints for one file's macro events, ascending by frontier
697/// and always starting at frontier zero (the compile-proven defines alone).
698///
699/// Checkpoints use an adaptive stride no greater than
700/// [`MACRO_ENVIRONMENT_CHECKPOINT_STRIDE`], and one lands directly after every
701/// `#include` event. Applying an include event replays the included file's
702/// complete event list, so keeping one there holds that unbounded cost out of
703/// every later replay window.
704struct MacroEnvironmentCheckpoints {
705    checkpoints: Vec<MacroEnvironmentCheckpoint>,
706}
707
708impl MacroEnvironmentCheckpoints {
709    /// The latest checkpoint at or before `frontier`.
710    fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
711        let index = self
712            .checkpoints
713            .partition_point(|checkpoint| checkpoint.frontier <= frontier);
714        assert!(
715            index > 0,
716            "a checkpoint vector starts at frontier zero, which precedes every request"
717        );
718        &self.checkpoints[index - 1]
719    }
720}
721
722impl MacroEnvironment {
723    fn binding(&self, name: &str) -> Option<&MacroBinding> {
724        self.bindings.get(name)
725    }
726
727    fn may_bind(&self, name: &str) -> bool {
728        self.bindings.contains_key(name) || self.unknown_names
729    }
730
731    fn insert(&mut self, name: String, binding: MacroBinding) {
732        self.known_undefined_names.remove(&name);
733        self.bindings.insert(name, binding);
734    }
735
736    fn remove(&mut self, name: &str) {
737        self.bindings.remove(name);
738        self.known_undefined_names.insert(name.to_string());
739    }
740
741    fn remove_known_undefined(&mut self, name: &str) {
742        self.known_undefined_names.remove(name);
743    }
744
745    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
746        for binding in self.bindings.values_mut() {
747            *binding = MacroBinding::uncertain_from(binding, source, byte);
748        }
749        self.known_undefined_names.clear();
750        // An untracked include could `#undef` a command-line define, so the
751        // may-hold filter must stop treating the build facts as decisive from
752        // here on. The additive proof path keeps its facts: they still hold at
753        // the include chain's activation point.
754        self.build_proven_defines.clear();
755        self.unknown_names = true;
756    }
757
758    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
759        guards.iter().all(|guard| self.guard_may_hold(guard))
760    }
761
762    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
763        let Some(expression) = guard.as_boolean_expression() else {
764            return true;
765        };
766        self.boolean_guard_may_hold(&expression)
767    }
768
769    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
770        match expression {
771            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
772            BooleanGuardExpression::Undefined(name) => {
773                self.bindings
774                    .get(name)
775                    .is_none_or(|binding| !binding.is_exact())
776                    && (!self.build_proven_defines.contains(name)
777                        || self.known_undefined_names.contains(name))
778            }
779            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
780            BooleanGuardExpression::Opaque(_)
781            | BooleanGuardExpression::NegatedOpaque(_)
782            | BooleanGuardExpression::Constant(true) => true,
783            BooleanGuardExpression::Constant(false) => false,
784            BooleanGuardExpression::All(expressions) => expressions
785                .iter()
786                .all(|expression| self.boolean_guard_may_hold(expression)),
787            BooleanGuardExpression::Any(expressions) => expressions
788                .iter()
789                .any(|expression| self.boolean_guard_may_hold(expression)),
790        }
791    }
792}
793
794#[derive(Clone)]
795pub enum EffectiveUsingTarget {
796    Ordinary {
797        name: String,
798        target_components: Vec<String>,
799        global: bool,
800    },
801    Namespace {
802        namespace_components: Vec<String>,
803        global: bool,
804    },
805}
806
807#[derive(Clone)]
808pub struct OrdinaryTypeImport {
809    pub target: EffectiveUsingTarget,
810    pub source: ProjectFile,
811    pub declaration_byte: usize,
812    pub scope_start: usize,
813    pub scope_end: usize,
814    pub scope_depth: usize,
815    pub block_scope: bool,
816    pub lexical_depth: usize,
817    pub declaration_namespace: Vec<String>,
818    pub namespace_scope: Option<Vec<String>>,
819    pub resolved_target_components: Option<Vec<String>>,
820    pub required_guards: HashSet<PreprocessorGuard>,
821}
822
823#[derive(Clone)]
824pub struct ConditionalIncludeProjection {
825    pub activation_byte: usize,
826    pub required_guards: HashSet<PreprocessorGuard>,
827    /// The subset of `required_guards` a lone `#if` contributes: a branch no
828    /// sibling `#else` completes, so no configuration is obliged to take it.
829    /// [`IncludePathAdmission::Compatible`] still demands those.
830    pub partial_guards: HashSet<PreprocessorGuard>,
831}
832
833#[derive(Default)]
834pub struct SourceUsingIndex {
835    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
836    pub directives: Vec<OrdinaryTypeImport>,
837}
838
839#[derive(Default)]
840pub struct ProjectUsingIndex {
841    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
842    pub directives: Vec<OrdinaryTypeImport>,
843}
844
845type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
846
847pub struct EffectiveUsingIndex {
848    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
849}
850
851impl EffectiveUsingIndex {
852    fn new(_root: ProjectFile) -> Self {
853        Self {
854            projected_by_name: Mutex::new(HashMap::default()),
855        }
856    }
857
858    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
859        self.projected_by_name
860            .lock()
861            .expect("C++ effective-using projection cache poisoned")
862            .entry(name.to_string())
863            .or_default()
864            .clone()
865    }
866}
867
868pub enum OrdinaryTypeImportResolution {
869    Resolved {
870        target: CodeUnit,
871        target_components: Vec<String>,
872        lexical_depth: usize,
873        is_direct: bool,
874    },
875    Ambiguous {
876        lexical_depth: usize,
877    },
878    Missing,
879}
880
881type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
882type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
883type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
884type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
885type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
886type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
887type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
888type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
889
890/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
891/// read from its declaration syntax rather than from its persisted signature
892/// string: the comparable shape of each parameter, and the trailing identity
893/// suffix that shape does not carry.
894struct ExtractedComparable {
895    shapes: Vec<CppComparableSlot>,
896    suffix: String,
897}
898
899/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
900/// before giving up on a written type name. A visited set already stops a
901/// cycle; this stops an adversarially long chain from costing a lookup per hop.
902const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
903
904/// Which comparison a conditional `#include` path's guards face before the
905/// header it reaches counts as visible at a reference.
906///
907/// `Proven` is the build's rule: `compile_commands.json` covers the
908/// reference's translation unit, so the reference's active guards plus the
909/// defines the build proves must imply every guard on the path, and a
910/// platform macro the build does not prove keeps the header invisible
911/// (#2011).
912///
913/// `Compatible` is the rule for a translation unit no build describes. A
914/// complete `#if`/`#else` family is a case analysis every configuration takes
915/// one branch of, so a branch of one is admitted unless its guards contradict
916/// the reference's own -- the rule the resolver already applies to a
917/// declaration's own guards (#2988). A lone `#if` is a different claim: no
918/// configuration is obliged to take it, so its guards stay strictly required
919/// and an unprovable one still reports `missing_compile_context` (#2011).
920///
921/// Without the first half, libuv's `uv/threadpool.h`, which `uv.h` includes
922/// from both arms of `#if defined(_WIN32)`, and `uv/unix.h`, which it includes
923/// from the `#else` arm, are invisible to every unguarded reference in the
924/// project (#3088).
925#[derive(Clone, Copy, Debug, PartialEq, Eq)]
926enum IncludePathAdmission {
927    Proven,
928    Compatible,
929}
930
931impl IncludePathAdmission {
932    fn admits(
933        self,
934        required: &HashSet<PreprocessorGuard>,
935        partial: &HashSet<PreprocessorGuard>,
936        reference_guards: Option<&HashSet<PreprocessorGuard>>,
937    ) -> bool {
938        match self {
939            Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
940            Self::Compatible => {
941                guard_requirements_hold_at_reference(partial, reference_guards)
942                    && guards_compatible_at_reference(required, reference_guards)
943            }
944        }
945    }
946}
947
948/// Per-query C++ visibility facts.
949///
950/// The analyzer is *borrowed*, never cloned: `TreeSitterAnalyzer::clone` gives
951/// the clone a fresh, empty `QueryReadCache` on purpose (clones cross
952/// generations and overlays, where another generation's hydrated states would
953/// be wrong). An index that owned a clone would therefore see an inactive read
954/// cache for every `prepared_syntax` call it makes, re-reading and re-parsing
955/// the same source from the store once per candidate instead of once per query
956/// — the #1175 blow-up, where one scan re-parsed a 4.8 MB generated header
957/// tens of thousands of times.
958pub struct VisibilityIndex<'a> {
959    cpp: &'a dyn CppSource,
960    /// Proof that the request scope the index was built under is still open.
961    /// The index is a per-query object whose lifetime is inside the scope's,
962    /// so carrying the token here instead of on ninety method signatures is
963    /// the same guarantee for far less plumbing (issue #2414 step 3).
964    token: QueryToken<'a>,
965    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
966    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
967    global_field_internal_linkage: HashMap<CodeUnit, bool>,
968    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
969    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
970    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
971    parser_alias_target_matches:
972        RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
973    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
974    project_using_index: OnceLock<ProjectUsingIndex>,
975    callable_reference_specs:
976        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
977    structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
978    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
979    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
980    include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
981    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
982    #[cfg(any(test, feature = "test-support"))]
983    conditional_include_projection_index_build_count: AtomicUsize,
984    #[cfg(any(test, feature = "test-support"))]
985    conditional_include_projection_state_count: AtomicUsize,
986    #[cfg(any(test, feature = "test-support"))]
987    conditional_include_target_state_count: AtomicUsize,
988    #[cfg(any(test, feature = "test-support"))]
989    include_activation_build_count: AtomicUsize,
990    #[cfg(any(test, feature = "test-support"))]
991    using_donor_activation_count: AtomicUsize,
992    #[cfg(any(test, feature = "test-support"))]
993    using_namespace_lookup_count: AtomicUsize,
994    #[cfg(any(test, feature = "test-support"))]
995    using_name_candidate_inspection_count: AtomicUsize,
996    #[cfg(any(test, feature = "test-support"))]
997    callable_reference_spec_build_count: AtomicUsize,
998    #[cfg(any(test, feature = "test-support"))]
999    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1000    #[cfg(any(test, feature = "test-support"))]
1001    visible_parser_alias_name_set_build_count: AtomicUsize,
1002    parser_alias_fallback_calls: AtomicUsize,
1003    parser_alias_fallback_files: AtomicUsize,
1004    parser_alias_source_parses: AtomicUsize,
1005    parser_alias_fallback_elapsed_micros: AtomicUsize,
1006    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1007    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1008    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1009    comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1010    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1011    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1012    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1013    c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1014    c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1015    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1016    macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1017    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1018    // The environment at selected event prefixes of each file, built once and read by every
1019    // worker. The authoritative differential shares this index across target workers whose
1020    // frontiers interleave arbitrarily and move backwards, so a forward cursor -- per worker or
1021    // not -- replayed a file's events once per backward request. Checkpoints answer any position
1022    // with a binary search and at most one stride of replay, and being immutable they need no
1023    // per-worker copy (#1496).
1024    macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1025    macro_replacements: Mutex<MacroReplacementCache>,
1026    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1027    pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1028    macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1029    macro_type_parameters: Mutex<MacroTypeParameterCache>,
1030    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1031    #[cfg(any(test, feature = "test-support"))]
1032    pub macro_replacement_parse_count: AtomicUsize,
1033    #[cfg(any(test, feature = "test-support"))]
1034    pub macro_event_application_count: AtomicUsize,
1035    /// How many files had their checkpoint vector built. One build per file
1036    /// per query even when workers race for the same file.
1037    #[cfg(any(test, feature = "test-support"))]
1038    pub macro_environment_checkpoint_build_count: AtomicUsize,
1039    /// How many requests landed off a checkpoint and so had to copy one and
1040    /// replay the events after it.
1041    #[cfg(any(test, feature = "test-support"))]
1042    pub macro_environment_copy_count: AtomicUsize,
1043    #[cfg(any(test, feature = "test-support"))]
1044    pub macro_environment_request_count: AtomicUsize,
1045    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1046    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1047    #[cfg(any(test, feature = "test-support"))]
1048    qualified_candidate_inspections: AtomicUsize,
1049    #[cfg(any(test, feature = "test-support"))]
1050    target_preserving_type_resolution_count: AtomicUsize,
1051    #[cfg(any(test, feature = "test-support"))]
1052    visibility_identifier_lookup_count: usize,
1053    #[cfg(any(test, feature = "test-support"))]
1054    visibility_identifier_batch_count: usize,
1055}
1056
1057impl Drop for VisibilityIndex<'_> {
1058    fn drop(&mut self) {
1059        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1060            return;
1061        }
1062        #[cfg(any(test, feature = "test-support"))]
1063        eprintln!(
1064            "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1065            self.macro_environment_request_count.load(Ordering::Relaxed),
1066            self.macro_environment_copy_count.load(Ordering::Relaxed),
1067            self.macro_environment_checkpoint_build_count
1068                .load(Ordering::Relaxed),
1069            self.macro_event_application_count.load(Ordering::Relaxed),
1070        );
1071        let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1072        if calls == 0 {
1073            return;
1074        }
1075        eprintln!(
1076            "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1077            calls,
1078            self.parser_alias_fallback_files.load(Ordering::Relaxed),
1079            self.parser_alias_source_parses.load(Ordering::Relaxed),
1080            self.parser_alias_fallback_elapsed_micros
1081                .load(Ordering::Relaxed)
1082                / 1_000,
1083        );
1084    }
1085}
1086
1087#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1088pub enum PreprocessorGuard {
1089    Defined(String),
1090    Undefined(String),
1091    Boolean(BooleanGuardExpression),
1092    Expression(String),
1093    NegatedExpression(String),
1094    Constant(bool),
1095}
1096
1097#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1098pub enum BooleanGuardExpression {
1099    Defined(String),
1100    Undefined(String),
1101    Truthy(String),
1102    Falsy(String),
1103    Opaque(String),
1104    NegatedOpaque(String),
1105    All(Vec<BooleanGuardExpression>),
1106    Any(Vec<BooleanGuardExpression>),
1107    Constant(bool),
1108}
1109
1110impl BooleanGuardExpression {
1111    fn negated(&self) -> Self {
1112        match self {
1113            Self::Defined(name) => Self::Undefined(name.clone()),
1114            Self::Undefined(name) => Self::Defined(name.clone()),
1115            Self::Truthy(name) => Self::Falsy(name.clone()),
1116            Self::Falsy(name) => Self::Truthy(name.clone()),
1117            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1118            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1119            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1120            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1121            Self::Constant(value) => Self::Constant(!value),
1122        }
1123    }
1124
1125    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1126        Self::normalized(expressions, true)
1127    }
1128
1129    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1130        Self::normalized(expressions, false)
1131    }
1132
1133    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1134        let mut normalized = Vec::new();
1135        for expression in expressions {
1136            match expression {
1137                Self::All(nested) if conjunction => normalized.extend(nested),
1138                Self::Any(nested) if !conjunction => normalized.extend(nested),
1139                Self::Constant(value) if value == conjunction => {}
1140                Self::Constant(value) => return Self::Constant(value),
1141                expression => normalized.push(expression),
1142            }
1143        }
1144        normalized.sort_unstable();
1145        normalized.dedup();
1146        match normalized.len() {
1147            0 => Self::Constant(conjunction),
1148            1 => normalized.pop().expect("one Boolean guard expression"),
1149            _ if conjunction => Self::All(normalized),
1150            _ => Self::Any(normalized),
1151        }
1152    }
1153
1154    fn implies(&self, required: &Self) -> bool {
1155        if self == required
1156            || matches!(self, Self::Constant(false))
1157            || matches!(required, Self::Constant(true))
1158        {
1159            return true;
1160        }
1161        if matches!(
1162            (self, required),
1163            (Self::Truthy(active), Self::Defined(required))
1164                | (Self::Undefined(active), Self::Falsy(required))
1165                if active == required
1166        ) {
1167            return true;
1168        }
1169        match self {
1170            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1171            Self::All(active) => match required {
1172                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1173                _ => active.iter().any(|expression| expression.implies(required)),
1174            },
1175            _ => match required {
1176                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1177                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1178                _ => false,
1179            },
1180        }
1181    }
1182
1183    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1184        match self {
1185            Self::Defined(name)
1186            | Self::Undefined(name)
1187            | Self::Truthy(name)
1188            | Self::Falsy(name) => name == macro_name,
1189            // Opaque expressions have structured conditional ownership but no
1190            // structured macro operands, so any mutation may change them.
1191            Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1192            Self::All(expressions) | Self::Any(expressions) => expressions
1193                .iter()
1194                .any(|expression| expression.may_depend_on_macro(macro_name)),
1195            Self::Constant(_) => false,
1196        }
1197    }
1198
1199    pub fn heap_size(&self) -> usize {
1200        match self {
1201            Self::Defined(value)
1202            | Self::Undefined(value)
1203            | Self::Truthy(value)
1204            | Self::Falsy(value)
1205            | Self::Opaque(value)
1206            | Self::NegatedOpaque(value) => value.len(),
1207            Self::All(expressions) | Self::Any(expressions) => {
1208                expressions
1209                    .iter()
1210                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1211                        size.saturating_add(std::mem::size_of::<Self>())
1212                            .saturating_add(expression.heap_size())
1213                    })
1214            }
1215            Self::Constant(_) => 0,
1216        }
1217    }
1218}
1219
1220impl PreprocessorGuard {
1221    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1222        match self {
1223            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1224            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1225            Self::Boolean(expression) => Some(expression.clone()),
1226            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1227            Self::Expression(_) | Self::NegatedExpression(_) => None,
1228        }
1229    }
1230
1231    fn negated(&self) -> Self {
1232        match self {
1233            Self::Defined(name) => Self::Undefined(name.clone()),
1234            Self::Undefined(name) => Self::Defined(name.clone()),
1235            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1236            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1237            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1238            Self::Constant(value) => Self::Constant(!value),
1239        }
1240    }
1241
1242    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1243        match self {
1244            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1245            Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1246            // These expressions could not be lowered to a Boolean operand
1247            // tree, so their dependencies remain unknown.
1248            Self::Expression(_) | Self::NegatedExpression(_) => true,
1249            Self::Constant(_) => false,
1250        }
1251    }
1252}
1253
1254#[derive(Clone, PartialEq, Eq)]
1255pub enum MacroDefinition {
1256    Object {
1257        replacement: String,
1258    },
1259    Function {
1260        parameters: Vec<String>,
1261        replacement: String,
1262    },
1263    VariadicFunction {
1264        parameters: Vec<String>,
1265        replacement: String,
1266    },
1267    Unsupported,
1268}
1269
1270#[derive(Clone, Debug, PartialEq, Eq)]
1271pub enum MacroIncludeProtection {
1272    MacroGuard(String),
1273    PragmaOnce,
1274    None,
1275}
1276
1277enum ParsedMacroReplacement {
1278    Parsed { source: String, tree: Tree },
1279    Unsupported,
1280}
1281
1282/// The sentinel that gives a function-like macro replacement a parseable
1283/// statement context. The replacement text is copied in verbatim, so the only
1284/// bytes ahead of it are this prefix.
1285const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1286
1287/// A function-like macro replacement parsed inside a sentinel function body.
1288///
1289/// Tree-sitter keeps a `#define NAME(a) ...` replacement as one opaque
1290/// `preproc_arg`. Wrapping that exact byte slice in a function body recovers
1291/// its statements, declarations, and member calls as ordinary C++ structure.
1292/// The slice is copied verbatim at [`Self::body_offset`], so a node range in
1293/// [`Self::tree`] maps back onto the defining `preproc_arg` by subtracting
1294/// that offset.
1295pub struct ParsedReplacementBody {
1296    pub source: String,
1297    pub tree: Tree,
1298    pub body_offset: usize,
1299    pub parameters: Vec<String>,
1300    /// For a normal parse this is an identity map. A constrained statement
1301    /// recovery may insert separators after a macro formal; each boundary in
1302    /// this map points back to the corresponding byte in the original
1303    /// replacement, keeping source-backed ranges exact after substitution.
1304    original_offsets: Box<[usize]>,
1305}
1306
1307impl ParsedReplacementBody {
1308    /// The sentinel function body holding the replacement's statements.
1309    pub fn statements(&self) -> Option<Node<'_>> {
1310        first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1311            .child_by_field_name("body")
1312    }
1313
1314    /// The byte range `node` occupies in the file that defines the macro.
1315    ///
1316    /// `replacement_start` is the logical replacement span's start byte.
1317    /// The origin map accounts for continuation whitespace and any synthetic
1318    /// statement separators inserted while parsing the replacement.
1319    pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1320        assert!(
1321            node.start_byte() >= self.body_offset,
1322            "synthetic sentinel node cannot be mapped to a macro replacement"
1323        );
1324        assert!(
1325            node.end_byte() >= self.body_offset,
1326            "synthetic sentinel node cannot be mapped to a macro replacement"
1327        );
1328        let start_offset = node.start_byte() - self.body_offset;
1329        let end_offset = node.end_byte() - self.body_offset;
1330        assert!(start_offset <= end_offset);
1331        let start_origin = *self
1332            .original_offsets
1333            .get(start_offset)
1334            .expect("replacement node start must have a source mapping");
1335        let end_origin = *self
1336            .original_offsets
1337            .get(end_offset)
1338            .expect("replacement node end must have a source mapping");
1339        assert!(start_origin <= end_origin);
1340        let start = replacement_start + start_origin;
1341        let end = replacement_start + end_origin;
1342        start..end
1343    }
1344
1345    /// Whether the replacement names the variadic argument pack.
1346    ///
1347    /// `__VA_ARGS__` parses as an ordinary identifier, so the sentinel tree
1348    /// gives no error for it even though the expansion it stands for is
1349    /// unknown at the definition. Reject it from the parsed tree rather than
1350    /// by scanning the replacement text.
1351    fn expands_variadic_arguments(&self) -> bool {
1352        let mut stack = vec![self.tree.root_node()];
1353        while let Some(node) = stack.pop() {
1354            if matches!(
1355                node.kind(),
1356                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1357            ) && node_text(node, &self.source) == "__VA_ARGS__"
1358            {
1359                return true;
1360            }
1361            push_named_children_reversed(node, &mut stack);
1362        }
1363        false
1364    }
1365}
1366
1367fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1368    let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1369    let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1370        if compact.starts_with("0x") || compact.starts_with("0X") {
1371            (16, 2, |ch| ch.is_ascii_hexdigit())
1372        } else if compact.starts_with("0b") || compact.starts_with("0B") {
1373            (2, 2, |ch| matches!(ch, '0' | '1'))
1374        } else if compact.starts_with('0') && compact.len() > 1 {
1375            (8, 0, |ch| matches!(ch, '0'..='7'))
1376        } else {
1377            (10, 0, |ch| ch.is_ascii_digit())
1378        };
1379    let digit_len = compact[digits_start..]
1380        .chars()
1381        .take_while(|ch| digit_matches(*ch))
1382        .map(char::len_utf8)
1383        .sum::<usize>();
1384    if digit_len == 0 {
1385        return None;
1386    }
1387    let digits_end = digits_start + digit_len;
1388    if !compact[digits_end..]
1389        .chars()
1390        .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1391    {
1392        return None;
1393    }
1394    i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1395}
1396
1397#[derive(Clone)]
1398enum MacroLocalBindingTypeTemplate {
1399    Parameter(usize),
1400    Fixed(String),
1401}
1402
1403#[derive(Clone)]
1404struct MacroLocalBindingTemplate {
1405    name: String,
1406    declared_type: MacroLocalBindingTypeTemplate,
1407    pointer_depth: i32,
1408}
1409
1410/// A local declaration contributed by one structurally known function-like macro.
1411///
1412/// `type_node` points into the invocation syntax when the replacement's type
1413/// is one of the macro parameters. Consumers can therefore use their normal
1414/// lexical type resolver without parsing replacement text themselves.
1415/// `proven_unit` carries a structured C-tag resolution when the grammar splits
1416/// an explicit `struct` or `union` argument across recovery nodes.
1417pub struct MacroLocalBinding<'tree> {
1418    pub name: String,
1419    pub type_name: String,
1420    pub type_node: Option<Node<'tree>>,
1421    pub pointer_depth: i32,
1422    pub proven_unit: Option<CodeUnit>,
1423}
1424
1425/// The source-backed declaration introduced by a function-like macro. The
1426/// declaration belongs to the macro definition, while the reference range
1427/// returned by [`VisibilityIndex::macro_lexical_references`] belongs to the
1428/// definition body, invocation argument, or caller scope where substitution
1429/// makes the binding visible.
1430#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1431pub enum MacroLexicalBindingKind {
1432    Parameter,
1433    Local,
1434}
1435
1436#[derive(Clone, Debug, PartialEq, Eq)]
1437pub struct MacroLexicalBinding {
1438    pub definition: ProjectFile,
1439    pub kind: MacroLexicalBindingKind,
1440    pub name: String,
1441    pub name_range: std::ops::Range<usize>,
1442    pub declaration_range: std::ops::Range<usize>,
1443}
1444
1445/// Lexical macro references are query-local facts. A cancelled or bounded
1446/// walk is kept distinct from a complete empty enumeration so authoritative
1447/// inverse callers can preserve their completeness status.
1448#[derive(Clone, Debug, Default, PartialEq, Eq)]
1449pub struct MacroLexicalReferences {
1450    pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1451    pub truncated: bool,
1452    pub cancelled: bool,
1453}
1454
1455fn macro_replacement_type_parameters(
1456    body: &ParsedReplacementBody,
1457    parameters: &[String],
1458) -> Option<Vec<usize>> {
1459    let mut found = Vec::new();
1460    let mut stack = vec![body.tree.root_node()];
1461    while let Some(node) = stack.pop() {
1462        let type_position = node.kind() == "type_identifier"
1463            || (node.kind() == "identifier"
1464                && node.parent().is_some_and(|parent| {
1465                    parent.kind() == "type_descriptor"
1466                        && parent.child_by_field_name("type") == Some(node)
1467                }));
1468        let offsetof_type_position = node.kind() == "identifier"
1469            && node
1470                .parent()
1471                .filter(|parent| parent.kind() == "argument_list")
1472                .and_then(|arguments| arguments.parent())
1473                .is_some_and(|call| {
1474                    call.kind() == "call_expression"
1475                        && call
1476                            .child_by_field_name("function")
1477                            .is_some_and(|function| {
1478                                function.kind() == "identifier"
1479                                    && node_text(function, &body.source) == "offsetof"
1480                            })
1481                        && call
1482                            .child_by_field_name("arguments")
1483                            .is_some_and(|arguments| {
1484                                argument_children(arguments).next() == Some(node)
1485                            })
1486                });
1487        if (type_position || offsetof_type_position)
1488            && let Some(index) = parameters
1489                .iter()
1490                .position(|parameter| parameter == node_text(node, &body.source))
1491            && !found.contains(&index)
1492        {
1493            found.push(index);
1494        }
1495        push_named_children_reversed(node, &mut stack);
1496    }
1497    (!found.is_empty()).then_some(found)
1498}
1499
1500fn macro_replacement_type_parameter(
1501    body: &ParsedReplacementBody,
1502    parameters: &[String],
1503) -> Option<usize> {
1504    let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1505    (parameters.len() == 1).then(|| parameters.pop().unwrap())
1506}
1507
1508pub(crate) fn macro_type_argument_node<'tree>(
1509    node: Node<'tree>,
1510    source: &str,
1511) -> Option<Node<'tree>> {
1512    match node.kind() {
1513        "type_descriptor" => {
1514            let type_child = node
1515                .child_by_field_name("type")
1516                .or_else(|| first_type_child(node))?;
1517            for index in (0..node.named_child_count()).rev() {
1518                let child = node.named_child(index)?;
1519                if child != type_child
1520                    && matches!(
1521                        child.kind(),
1522                        "identifier"
1523                            | "type_identifier"
1524                            | "qualified_identifier"
1525                            | "scoped_type_identifier"
1526                    )
1527                {
1528                    return Some(child);
1529                }
1530            }
1531            macro_type_argument_node(type_child, source)
1532        }
1533        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1534            node.child_by_field_name("name")
1535        }
1536        "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1537            .next_named_sibling()
1538            .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1539            .and_then(|error| error.named_child(0))
1540            .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1541        _ => cpp_name_component_nodes(node).is_some().then_some(node),
1542    }
1543}
1544
1545fn c_function_macro_argument<'tree>(
1546    node: Node<'tree>,
1547) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1548    let mut current = node;
1549    while let Some(parent) = current.parent() {
1550        if parent.kind() == "argument_list" {
1551            let call = parent.parent().filter(|call| {
1552                call.kind() == "call_expression"
1553                    && call.child_by_field_name("arguments") == Some(parent)
1554                    && call
1555                        .child_by_field_name("function")
1556                        .is_some_and(|function| {
1557                            function.kind() == "identifier"
1558                                && !node_range_contains(function, current)
1559                        })
1560            })?;
1561            let mut actuals = argument_children(parent).enumerate();
1562            let (index, argument) = actuals.find(|(_, argument)| {
1563                node_range_contains(*argument, node)
1564                    && (argument.start_byte() == current.start_byte()
1565                        || argument.end_byte() == current.end_byte())
1566            })?;
1567            return Some((call, index, argument));
1568        }
1569        current = parent;
1570    }
1571    None
1572}
1573
1574/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1575/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1576/// invocation as the assignment's left operand and the declared name as one
1577/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1578fn recognized_c_macro_declarator_binding<'tree>(
1579    statement: Node<'tree>,
1580    source: &str,
1581) -> Option<MacroLocalBinding<'tree>> {
1582    let assignment = match statement.kind() {
1583        "assignment_expression" => statement,
1584        "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1585        _ => return None,
1586    };
1587    if assignment.kind() != "assignment_expression" {
1588        return None;
1589    }
1590    let call = assignment.child_by_field_name("left")?;
1591    if call.kind() != "call_expression" {
1592        return None;
1593    }
1594    let function = call.child_by_field_name("function")?;
1595    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1596        return None;
1597    }
1598    let arguments = call.child_by_field_name("arguments")?;
1599    let mut actuals = argument_children(arguments);
1600    let type_node = actuals.next()?;
1601    if actuals.next().is_some()
1602        || !matches!(
1603            type_node.kind(),
1604            "identifier"
1605                | "type_identifier"
1606                | "qualified_identifier"
1607                | "scoped_type_identifier"
1608                | "template_type"
1609        )
1610    {
1611        return None;
1612    }
1613    let name_node = (0..assignment.named_child_count())
1614        .filter_map(|index| assignment.named_child(index))
1615        .filter(|child| child.kind() == "ERROR")
1616        .filter_map(|error| {
1617            (error.named_child_count() == 1)
1618                .then(|| error.named_child(0))
1619                .flatten()
1620        })
1621        .find(|node| node.kind() == "identifier")?;
1622    let name = node_text(name_node, source).trim();
1623    let type_name = node_text(type_node, source).trim();
1624    if name.is_empty() || type_name.is_empty() {
1625        return None;
1626    }
1627    Some(MacroLocalBinding {
1628        name: name.to_string(),
1629        type_name: type_name.to_string(),
1630        type_node: Some(type_node),
1631        pointer_depth: 1,
1632        proven_unit: None,
1633    })
1634}
1635
1636#[derive(Clone, PartialEq, Eq)]
1637pub struct MacroBinding {
1638    source: ProjectFile,
1639    declaration_byte: usize,
1640    definition: MacroDefinition,
1641    exact: bool,
1642}
1643
1644impl MacroBinding {
1645    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1646        Self {
1647            source: source.clone(),
1648            declaration_byte,
1649            definition: MacroDefinition::Unsupported,
1650            exact: false,
1651        }
1652    }
1653
1654    fn is_exact(&self) -> bool {
1655        self.exact
1656    }
1657
1658    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1659        Self {
1660            source: source.clone(),
1661            declaration_byte,
1662            definition: current.definition.clone(),
1663            exact: false,
1664        }
1665    }
1666}
1667
1668/// The preprocessor conditionals whose truth decides whether one macro event
1669/// applies, by the start byte of each conditional node. Empty means the event
1670/// is unconditional.
1671///
1672/// [`VisibilityIndex::macro_event_condition_value`] needs exactly the
1673/// conditional ancestors that structurally contain the event, and deciding
1674/// containment means asking [`cpp_displaced_preprocessor_boundary`] for an
1675/// `#endif` tree-sitter displaced into error recovery -- a walk of the
1676/// conditional's whole subtree. That answer is a fact about the tree alone, so
1677/// it is settled once, when the events are collected, instead of on every
1678/// replay of the event (#1496).
1679type OwningPreprocessorConditionals = Box<[usize]>;
1680
1681#[derive(Clone)]
1682pub enum MacroEvent {
1683    Define {
1684        name: String,
1685        binding: MacroBinding,
1686        byte: usize,
1687        conditionals: OwningPreprocessorConditionals,
1688    },
1689    Undef {
1690        name: String,
1691        byte: usize,
1692        conditionals: OwningPreprocessorConditionals,
1693    },
1694    Include {
1695        targets: Vec<ProjectFile>,
1696        byte: usize,
1697        conditionals: OwningPreprocessorConditionals,
1698    },
1699    Invalidate {
1700        byte: usize,
1701    },
1702}
1703
1704impl MacroEvent {
1705    pub fn byte(&self) -> usize {
1706        match self {
1707            Self::Define { byte, .. }
1708            | Self::Undef { byte, .. }
1709            | Self::Include { byte, .. }
1710            | Self::Invalidate { byte } => *byte,
1711        }
1712    }
1713}
1714
1715#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1716pub enum CallArityEvidence {
1717    Exact(usize),
1718    Unknown,
1719}
1720
1721impl CallArityEvidence {
1722    pub fn exact(self) -> Option<usize> {
1723        match self {
1724            Self::Exact(arity) => Some(arity),
1725            Self::Unknown => None,
1726        }
1727    }
1728
1729    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1730        self.exact().map(|arity| expected.accepts(arity))
1731    }
1732}
1733
1734#[derive(Clone)]
1735struct DeclaredFieldTypeFact {
1736    type_text: String,
1737    indirection: i32,
1738    template_arguments: Option<Vec<CppTemplateExpression>>,
1739}
1740
1741#[derive(Clone, PartialEq, Eq)]
1742enum StructuredAliasTarget {
1743    Builtin,
1744    Named {
1745        components: Vec<String>,
1746        global: bool,
1747        arguments: Option<Vec<CppTemplateExpression>>,
1748    },
1749}
1750
1751struct CppAlias {
1752    name: String,
1753    target: String,
1754    namespace: Option<String>,
1755}
1756
1757type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1758
1759/// Why template-argument resolution failed. Definition diagnostics render
1760/// each mode differently; graph scans only care that the resolution is
1761/// unproven and match `Err(_)`.
1762#[derive(Debug, Clone, PartialEq, Eq)]
1763pub enum CppTemplateResolutionError {
1764    /// A template alias expansion revisited `alias`.
1765    AliasCycle { alias: CodeUnit },
1766    /// The explicit arguments do not bind to the declared template parameters.
1767    ArgumentBinding,
1768    /// Bound arguments do not substitute into the alias target's arguments.
1769    Substitution,
1770    /// No visible primary template declaration could be selected and
1771    /// reconciled for the specialization family.
1772    PrimarySelection,
1773    /// More than one applicable specialization remains and none is strictly
1774    /// more specialized than every other candidate.
1775    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1776}
1777
1778/// The ambiguity candidates, deduplicated to one representative per visible
1779/// symbol so a diagnostic lists each contender once.
1780fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1781    let mut distinct: Vec<CodeUnit> = Vec::new();
1782    for unit in units {
1783        if !distinct
1784            .iter()
1785            .any(|existing| same_visible_symbol(existing, unit))
1786        {
1787            distinct.push(unit.clone());
1788        }
1789    }
1790    distinct
1791}
1792
1793/// Enumerate macro lexical references while constructing visibility only if
1794/// the bounded source walk finds a definition or identifier call candidate.
1795pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1796    visibility: Factory,
1797    file: &ProjectFile,
1798    root: Node<'_>,
1799    source: &str,
1800    max_references: usize,
1801    cancelled: impl FnMut() -> bool,
1802) -> MacroLexicalReferences
1803where
1804    Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1805{
1806    crate::graph::macro_lexical::all_references(
1807        visibility,
1808        file,
1809        root,
1810        source,
1811        max_references,
1812        cancelled,
1813    )
1814}
1815
1816impl<'a> VisibilityIndex<'a> {
1817    pub fn cpp(&self) -> &'a dyn CppSource {
1818        self.cpp
1819    }
1820
1821    /// The request-scope proof this index was built with (issue #2414 step 3).
1822    pub fn token(&self) -> QueryToken<'a> {
1823        self.token
1824    }
1825
1826    /// Whether `file` has a structured unresolved include that is active before
1827    /// the reference at `before_byte`.
1828    ///
1829    /// Include facts are collected from the prepared tree once per visibility
1830    /// query. The reference position is still evaluated for each call because
1831    /// preprocessor visibility depends on the reference's own conditional
1832    /// context.
1833    pub fn has_unresolved_include_visible_before(
1834        &self,
1835        file: &ProjectFile,
1836        before_byte: usize,
1837    ) -> bool {
1838        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
1839            return false;
1840        };
1841        let cell = self
1842            .structured_include_fact_cells
1843            .lock()
1844            .expect("C++ structured include-fact cache poisoned")
1845            .entry(file.clone())
1846            .or_default()
1847            .clone();
1848        let facts = cell.get_or_init(|| collect_structured_include_facts(prepared.as_ref()));
1849        has_unresolved_include_visible_before_in_prepared(
1850            file,
1851            prepared.as_ref(),
1852            self.cpp.include_target_index(),
1853            facts,
1854            before_byte,
1855        )
1856    }
1857
1858    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1859    /// bypassing the include-closure walk [`Self::build`] performs.
1860    ///
1861    /// The resolver's own unit tests drive the type-resolution paths against a
1862    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1863    /// because they need a real `CppAnalyzer`, so the struct literal they used
1864    /// to write inline is here instead of thirty-three public fields.
1865    #[cfg(any(test, feature = "test-support"))]
1866    pub fn from_visible_files_for_test(
1867        cpp: &'a dyn CppSource,
1868        token: QueryToken<'a>,
1869        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1870    ) -> Self {
1871        let visible_source_files_by_root = visible_by_file
1872            .iter()
1873            .map(|(file, visible)| {
1874                (
1875                    file.clone(),
1876                    visible
1877                        .iter()
1878                        .map(|unit| unit.source().clone())
1879                        .chain(std::iter::once(file.clone()))
1880                        .collect(),
1881                )
1882            })
1883            .collect();
1884        let mut global_field_internal_linkage = HashMap::default();
1885        Self {
1886            cpp,
1887            token,
1888            visible_by_identifier: build_visible_identifier_index(
1889                &CppGraphSource::from_source(cpp, token),
1890                &visible_by_file,
1891                &visible_source_files_by_root,
1892                &mut global_field_internal_linkage,
1893            ),
1894            global_field_internal_linkage,
1895            visible_by_file,
1896            visible_source_files_by_root,
1897            alias_cells: Mutex::new(HashMap::default()),
1898            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1899            parser_alias_target_matches: RwLock::new(HashMap::default()),
1900            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1901            project_using_index: OnceLock::new(),
1902            callable_reference_specs: Mutex::new(HashMap::default()),
1903            structured_include_fact_cells: Mutex::new(HashMap::default()),
1904            include_activation_cells: Mutex::new(HashMap::default()),
1905            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1906            include_path_admission_cells: Mutex::new(HashMap::default()),
1907            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1908            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1909            conditional_include_projection_state_count: AtomicUsize::new(0),
1910            conditional_include_target_state_count: AtomicUsize::new(0),
1911            include_activation_build_count: AtomicUsize::new(0),
1912            using_donor_activation_count: AtomicUsize::new(0),
1913            using_namespace_lookup_count: AtomicUsize::new(0),
1914            using_name_candidate_inspection_count: AtomicUsize::new(0),
1915            callable_reference_spec_build_count: AtomicUsize::new(0),
1916            alias_source_parse_counts: Mutex::new(HashMap::default()),
1917            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1918            parser_alias_fallback_calls: AtomicUsize::new(0),
1919            parser_alias_fallback_files: AtomicUsize::new(0),
1920            parser_alias_source_parses: AtomicUsize::new(0),
1921            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1922            field_type_facts: Mutex::new(HashMap::default()),
1923            structured_alias_targets: Mutex::new(HashMap::default()),
1924            callable_comparables: Mutex::new(HashMap::default()),
1925            comparable_name_declarations: Mutex::new(HashMap::default()),
1926            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1927            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1928            precise_parent_cache: Mutex::new(HashMap::default()),
1929            c_tag_kind_cache: Mutex::new(HashMap::default()),
1930            c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
1931            macro_event_cells: Mutex::new(HashMap::default()),
1932            macro_event_name_sets: Mutex::new(HashMap::default()),
1933            macro_include_protection_cells: Mutex::new(HashMap::default()),
1934            macro_environment_checkpoints: Mutex::new(HashMap::default()),
1935            macro_replacements: Mutex::new(HashMap::default()),
1936            macro_local_binding_templates: Mutex::new(HashMap::default()),
1937            macro_lexical_templates: Mutex::new(HashMap::default()),
1938            macro_replacement_bodies: Mutex::new(HashMap::default()),
1939            macro_type_parameters: Mutex::new(HashMap::default()),
1940            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1941            macro_replacement_parse_count: AtomicUsize::new(0),
1942            macro_event_application_count: AtomicUsize::new(0),
1943            macro_environment_checkpoint_build_count: AtomicUsize::new(0),
1944            macro_environment_copy_count: AtomicUsize::new(0),
1945            macro_environment_request_count: AtomicUsize::new(0),
1946            cpp_template_metadata: HashMap::default(),
1947            cpp_template_families: HashMap::default(),
1948            qualified_candidate_inspections: AtomicUsize::new(0),
1949            target_preserving_type_resolution_count: AtomicUsize::new(0),
1950            visibility_identifier_lookup_count: 0,
1951            visibility_identifier_batch_count: 0,
1952        }
1953    }
1954
1955    /// The index's own C++ source, in the dispatching-analyzer shape.
1956    ///
1957    /// Four resolution paths reach the workspace through the C++ analyzer they
1958    /// already hold rather than through the analyzer the query was issued
1959    /// against; before the move they passed `&CppAnalyzer` straight into a
1960    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
1961    fn cpp_source(&self) -> CppGraphSource<'a> {
1962        CppGraphSource::from_source(self.cpp, self.token)
1963    }
1964
1965    pub fn build(
1966        cpp: &'a dyn CppSource,
1967        token: QueryToken<'a>,
1968        analyzer: &CppGraphSource<'_>,
1969        roots: &HashSet<ProjectFile>,
1970    ) -> Self {
1971        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1972    }
1973
1974    pub fn build_with_cancellation(
1975        cpp: &'a dyn CppSource,
1976        token: QueryToken<'a>,
1977        analyzer: &CppGraphSource<'_>,
1978        roots: &HashSet<ProjectFile>,
1979        cancellation: Option<&CancellationToken>,
1980    ) -> Self {
1981        let visibility_started = Instant::now();
1982        let include_targets = cpp.include_target_index();
1983        let includes_started = Instant::now();
1984        let mut include_graph = IncludeGraph::default();
1985        for root in roots {
1986            include_graph.extend_with(root, cancellation, &mut |file| {
1987                cpp_include_paths(&cpp.visibility_import_statements(token, file))
1988                    .into_iter()
1989                    .flat_map(|include| {
1990                        resolve_include_targets_with_index(file, &include, include_targets)
1991                    })
1992                    .collect()
1993            });
1994        }
1995        let include_elapsed = includes_started.elapsed();
1996        let include_file_count = include_graph.files().count();
1997        let visible_source_files_by_root = roots
1998            .iter()
1999            .map(|root| {
2000                (
2001                    root.clone(),
2002                    include_graph.reachable_files(root, cancellation),
2003                )
2004            })
2005            .collect::<HashMap<_, _>>();
2006        let mut visibility_stats = BoundedVisibilityStats::default();
2007        let mut visible_by_file = build_bounded_visible_declarations(
2008            cpp,
2009            token,
2010            analyzer,
2011            roots,
2012            &visible_source_files_by_root,
2013            cancellation,
2014            &mut visibility_stats,
2015        );
2016        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2017            eprintln!(
2018                "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={}",
2019                visibility_started.elapsed().as_millis(),
2020                include_elapsed.as_millis(),
2021                include_file_count,
2022                visibility_stats.rounds,
2023                visibility_stats.root_names,
2024                visibility_stats.identifier_lookups,
2025                visibility_stats.identifier_batches,
2026                visibility_stats.candidate_units,
2027                visibility_stats.candidate_sources,
2028                visibility_stats.declaration_reads,
2029                visibility_stats.declaration_units,
2030                visibility_stats.selected_units,
2031                visibility_stats.dependency_ast_nodes,
2032                visibility_stats.dependency_names,
2033                visibility_stats.lookup_elapsed.as_millis(),
2034                visibility_stats.declaration_elapsed.as_millis(),
2035                visibility_stats.dependency_ast_elapsed.as_millis(),
2036            );
2037        }
2038        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2039        let finalize_started = Instant::now();
2040        if report_stats {
2041            eprintln!(
2042                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2043                visible_by_file.len(),
2044                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2045            );
2046        }
2047        let owner_started = Instant::now();
2048        if report_stats {
2049            eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2050        }
2051        let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2052        if report_stats {
2053            eprintln!(
2054                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2055                owner_stats.unseen_owners,
2056                owner_stats.definition_lookups,
2057                owner_stats.admitted,
2058                owner_started.elapsed().as_millis(),
2059            );
2060        }
2061        let mut global_field_internal_linkage = HashMap::default();
2062        let identifier_started = Instant::now();
2063        if report_stats {
2064            eprintln!(
2065                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2066            );
2067        }
2068        let visible_by_identifier = build_visible_identifier_index(
2069            analyzer,
2070            &visible_by_file,
2071            &visible_source_files_by_root,
2072            &mut global_field_internal_linkage,
2073        );
2074        if report_stats {
2075            eprintln!(
2076                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2077                visible_by_identifier.len(),
2078                visible_by_identifier
2079                    .values()
2080                    .map(HashMap::len)
2081                    .sum::<usize>(),
2082                visible_by_identifier
2083                    .values()
2084                    .flat_map(HashMap::values)
2085                    .map(Vec::len)
2086                    .sum::<usize>(),
2087                identifier_started.elapsed().as_millis(),
2088            );
2089        }
2090        let mut cpp_template_metadata = HashMap::default();
2091        let metadata_started = Instant::now();
2092        let mut template_classes = 0usize;
2093        if report_stats {
2094            eprintln!(
2095                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2096            );
2097        }
2098        for unit in visible_by_file
2099            .values()
2100            .flatten()
2101            .filter(|unit| unit.is_class())
2102        {
2103            template_classes += 1;
2104            if cpp_template_metadata.contains_key(unit) {
2105                continue;
2106            }
2107            if let Some(metadata) = cpp.template_metadata(unit) {
2108                cpp_template_metadata.insert(unit.clone(), metadata);
2109            }
2110        }
2111        if report_stats {
2112            eprintln!(
2113                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2114                template_classes,
2115                cpp_template_metadata.len(),
2116                metadata_started.elapsed().as_millis(),
2117            );
2118        }
2119        let families_started = Instant::now();
2120        if report_stats {
2121            eprintln!(
2122                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2123            );
2124        }
2125        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2126        for (unit, metadata) in &cpp_template_metadata {
2127            cpp_template_families
2128                .entry(metadata.primary_fq_name.clone())
2129                .or_default()
2130                .push(unit.clone());
2131        }
2132        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
2133        // order above is a function of those hashes. Two mirrored headers can
2134        // declare one specialization; `select_template_specialization` treats
2135        // them as interchangeable and returns the family's first entry, so an
2136        // unsorted family made the reported declaration depend on the
2137        // workspace's absolute path and on unrelated files (#1836). Order the
2138        // family exactly as `build_visible_identifier_index` orders its
2139        // per-identifier candidate lists.
2140        for family in cpp_template_families.values_mut() {
2141            sort_lookup_units(family);
2142        }
2143        if report_stats {
2144            eprintln!(
2145                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2146                cpp_template_families.len(),
2147                cpp_template_families.values().map(Vec::len).sum::<usize>(),
2148                families_started.elapsed().as_millis(),
2149            );
2150            eprintln!(
2151                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2152                visible_by_file.len(),
2153                visible_by_file.values().map(HashSet::len).sum::<usize>(),
2154                finalize_started.elapsed().as_millis(),
2155                visibility_started.elapsed().as_millis(),
2156            );
2157        }
2158        Self {
2159            cpp,
2160            token,
2161            visible_by_file,
2162            visible_by_identifier,
2163            global_field_internal_linkage,
2164            visible_source_files_by_root,
2165            alias_cells: Mutex::new(HashMap::default()),
2166            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2167            parser_alias_target_matches: RwLock::new(HashMap::default()),
2168            ordinary_type_import_cells: Mutex::new(HashMap::default()),
2169            project_using_index: OnceLock::new(),
2170            callable_reference_specs: Mutex::new(HashMap::default()),
2171            structured_include_fact_cells: Mutex::new(HashMap::default()),
2172            include_activation_cells: Mutex::new(HashMap::default()),
2173            compile_proven_guard_cells: Mutex::new(HashMap::default()),
2174            include_path_admission_cells: Mutex::new(HashMap::default()),
2175            conditional_include_projection_cells: Mutex::new(HashMap::default()),
2176            #[cfg(any(test, feature = "test-support"))]
2177            conditional_include_projection_index_build_count: AtomicUsize::new(0),
2178            #[cfg(any(test, feature = "test-support"))]
2179            conditional_include_projection_state_count: AtomicUsize::new(0),
2180            #[cfg(any(test, feature = "test-support"))]
2181            conditional_include_target_state_count: AtomicUsize::new(0),
2182            #[cfg(any(test, feature = "test-support"))]
2183            include_activation_build_count: AtomicUsize::new(0),
2184            #[cfg(any(test, feature = "test-support"))]
2185            using_donor_activation_count: AtomicUsize::new(0),
2186            #[cfg(any(test, feature = "test-support"))]
2187            using_namespace_lookup_count: AtomicUsize::new(0),
2188            #[cfg(any(test, feature = "test-support"))]
2189            using_name_candidate_inspection_count: AtomicUsize::new(0),
2190            #[cfg(any(test, feature = "test-support"))]
2191            callable_reference_spec_build_count: AtomicUsize::new(0),
2192            #[cfg(any(test, feature = "test-support"))]
2193            alias_source_parse_counts: Mutex::new(HashMap::default()),
2194            #[cfg(any(test, feature = "test-support"))]
2195            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2196            parser_alias_fallback_calls: AtomicUsize::new(0),
2197            parser_alias_fallback_files: AtomicUsize::new(0),
2198            parser_alias_source_parses: AtomicUsize::new(0),
2199            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2200            field_type_facts: Mutex::new(HashMap::default()),
2201            structured_alias_targets: Mutex::new(HashMap::default()),
2202            callable_comparables: Mutex::new(HashMap::default()),
2203            comparable_name_declarations: Mutex::new(HashMap::default()),
2204            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2205            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2206            precise_parent_cache: Mutex::new(HashMap::default()),
2207            c_tag_kind_cache: Mutex::new(HashMap::default()),
2208            c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2209            macro_event_cells: Mutex::new(HashMap::default()),
2210            macro_event_name_sets: Mutex::new(HashMap::default()),
2211            macro_include_protection_cells: Mutex::new(HashMap::default()),
2212            macro_environment_checkpoints: Mutex::new(HashMap::default()),
2213            macro_replacements: Mutex::new(HashMap::default()),
2214            macro_local_binding_templates: Mutex::new(HashMap::default()),
2215            macro_lexical_templates: Mutex::new(HashMap::default()),
2216            macro_replacement_bodies: Mutex::new(HashMap::default()),
2217            macro_type_parameters: Mutex::new(HashMap::default()),
2218            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2219            #[cfg(any(test, feature = "test-support"))]
2220            macro_replacement_parse_count: AtomicUsize::new(0),
2221            #[cfg(any(test, feature = "test-support"))]
2222            macro_event_application_count: AtomicUsize::new(0),
2223            #[cfg(any(test, feature = "test-support"))]
2224            macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2225            #[cfg(any(test, feature = "test-support"))]
2226            macro_environment_copy_count: AtomicUsize::new(0),
2227            #[cfg(any(test, feature = "test-support"))]
2228            macro_environment_request_count: AtomicUsize::new(0),
2229            cpp_template_metadata,
2230            cpp_template_families,
2231            #[cfg(any(test, feature = "test-support"))]
2232            qualified_candidate_inspections: AtomicUsize::new(0),
2233            #[cfg(any(test, feature = "test-support"))]
2234            target_preserving_type_resolution_count: AtomicUsize::new(0),
2235            #[cfg(any(test, feature = "test-support"))]
2236            visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2237            #[cfg(any(test, feature = "test-support"))]
2238            visibility_identifier_batch_count: visibility_stats.identifier_batches,
2239        }
2240    }
2241
2242    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2243        if file == target.source() {
2244            return true;
2245        }
2246        if self.global_field_has_internal_linkage(target) {
2247            return self
2248                .visible_source_files_by_root
2249                .get(file)
2250                .is_some_and(|sources| sources.contains(target.source()));
2251        }
2252        self.visible_by_file
2253            .get(file)
2254            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2255    }
2256
2257    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2258        self.global_field_internal_linkage
2259            .get(unit)
2260            .copied()
2261            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2262    }
2263
2264    pub fn call_arity_evidence(
2265        &self,
2266        file: &ProjectFile,
2267        call: Node<'_>,
2268        source: &str,
2269    ) -> CallArityEvidence {
2270        self.call_arity_evidence_at(file, call, source, call.start_byte())
2271    }
2272
2273    /// Argument-count evidence for a call whose macro environment is not the
2274    /// one at its own byte offset.
2275    ///
2276    /// A call recovered from a macro replacement lives in a sentinel parse of
2277    /// its own, so its node offsets say nothing about which macros are active.
2278    /// `environment_byte` names the position in `file` whose macro environment
2279    /// governs the call: the macro definition site for a replacement body.
2280    pub fn call_arity_evidence_at(
2281        &self,
2282        file: &ProjectFile,
2283        call: Node<'_>,
2284        source: &str,
2285        environment_byte: usize,
2286    ) -> CallArityEvidence {
2287        let Some(arguments) = call
2288            .child_by_field_name("arguments")
2289            .or_else(|| call.child_by_field_name("parameters"))
2290            .or_else(|| call.child_by_field_name("value"))
2291            .or_else(|| first_named_child_of_kind(call, "argument_list"))
2292            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2293        else {
2294            return CallArityEvidence::Exact(0);
2295        };
2296        let recovered_c_keyword_arguments =
2297            recovered_c_keyword_argument_count(file, call, arguments, source);
2298        let c_semantics = reference_uses_c_semantics(self.cpp, file);
2299        let arguments = argument_children(arguments)
2300            .flat_map(|argument| {
2301                recovered_c_new_expression_arguments(argument, c_semantics)
2302                    .map(Vec::from)
2303                    .unwrap_or_else(|| vec![argument])
2304            })
2305            .collect::<Vec<_>>();
2306        if arguments
2307            .iter()
2308            .all(|argument| !argument_shape_may_change_arity(*argument))
2309        {
2310            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2311        }
2312        let environment = self.macro_environment(file, environment_byte);
2313        let mut stack = Vec::new();
2314        let mut total = recovered_c_keyword_arguments;
2315        for argument in arguments {
2316            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2317                return CallArityEvidence::Unknown;
2318            }
2319            let CallArityEvidence::Exact(spread) =
2320                self.argument_arity_evidence(argument, source, &environment, &mut stack)
2321            else {
2322                return CallArityEvidence::Unknown;
2323            };
2324            total += spread;
2325        }
2326        CallArityEvidence::Exact(total)
2327    }
2328
2329    fn argument_arity_evidence(
2330        &self,
2331        argument: Node<'_>,
2332        source: &str,
2333        environment: &MacroEnvironment,
2334        stack: &mut Vec<(ProjectFile, usize)>,
2335    ) -> CallArityEvidence {
2336        let (name, invocation_arguments, function_like) = match argument.kind() {
2337            "identifier" => (node_text(argument, source), None, false),
2338            "call_expression" => {
2339                let Some(function) = argument.child_by_field_name("function") else {
2340                    return CallArityEvidence::Exact(1);
2341                };
2342                if function.kind() != "identifier" {
2343                    return CallArityEvidence::Exact(1);
2344                }
2345                let Some(arguments) = argument.child_by_field_name("arguments") else {
2346                    return CallArityEvidence::Exact(1);
2347                };
2348                (node_text(function, source), Some(arguments), true)
2349            }
2350            _ => return CallArityEvidence::Exact(1),
2351        };
2352        let Some(binding) = environment.binding(name) else {
2353            return if environment.unknown_names {
2354                CallArityEvidence::Unknown
2355            } else {
2356                CallArityEvidence::Exact(1)
2357            };
2358        };
2359        if !binding.is_exact() {
2360            return CallArityEvidence::Unknown;
2361        }
2362        match (&binding.definition, invocation_arguments, function_like) {
2363            (MacroDefinition::Object { replacement }, None, false) => self
2364                .replacement_arity_evidence(
2365                    replacement,
2366                    &[],
2367                    &[],
2368                    source,
2369                    environment,
2370                    stack,
2371                    binding,
2372                ),
2373            (
2374                MacroDefinition::Function {
2375                    parameters,
2376                    replacement,
2377                },
2378                Some(arguments),
2379                true,
2380            ) => {
2381                let actuals = argument_children(arguments).collect::<Vec<_>>();
2382                if actuals.len() != parameters.len() {
2383                    CallArityEvidence::Unknown
2384                } else {
2385                    self.replacement_arity_evidence(
2386                        replacement,
2387                        parameters,
2388                        &actuals,
2389                        source,
2390                        environment,
2391                        stack,
2392                        binding,
2393                    )
2394                }
2395            }
2396            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2397            _ => CallArityEvidence::Unknown,
2398        }
2399    }
2400
2401    #[allow(clippy::too_many_arguments)]
2402    fn replacement_arity_evidence(
2403        &self,
2404        replacement: &str,
2405        parameters: &[String],
2406        actuals: &[Node<'_>],
2407        actual_source: &str,
2408        environment: &MacroEnvironment,
2409        stack: &mut Vec<(ProjectFile, usize)>,
2410        binding: &MacroBinding,
2411    ) -> CallArityEvidence {
2412        let identity = (binding.source.clone(), binding.declaration_byte);
2413        if stack.contains(&identity) || replacement.trim().is_empty() {
2414            return CallArityEvidence::Unknown;
2415        }
2416        stack.push(identity);
2417        let parsed = self.parsed_macro_replacement(binding, replacement);
2418        let evidence = (|| {
2419            let ParsedMacroReplacement::Parsed {
2420                source: sentinel,
2421                tree,
2422            } = parsed.as_ref()
2423            else {
2424                return None;
2425            };
2426            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2427            let arguments = call.child_by_field_name("arguments")?;
2428            let mut total = 0usize;
2429            for argument in argument_children(arguments) {
2430                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2431                    return None;
2432                }
2433                if argument.kind() == "identifier"
2434                    && let Some(parameter_index) = parameters
2435                        .iter()
2436                        .position(|parameter| parameter == node_text(argument, sentinel))
2437                {
2438                    if !macro_expansion_shape_is_safe(
2439                        actuals[parameter_index],
2440                        actual_source,
2441                        &[],
2442                        environment,
2443                    ) {
2444                        return None;
2445                    }
2446                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2447                        actuals[parameter_index],
2448                        actual_source,
2449                        environment,
2450                        stack,
2451                    ) else {
2452                        return None;
2453                    };
2454                    total += spread;
2455                    continue;
2456                }
2457                let CallArityEvidence::Exact(spread) =
2458                    self.argument_arity_evidence(argument, sentinel, environment, stack)
2459                else {
2460                    return None;
2461                };
2462                total += spread;
2463            }
2464            Some(CallArityEvidence::Exact(total))
2465        })()
2466        .unwrap_or(CallArityEvidence::Unknown);
2467        stack.pop();
2468        evidence
2469    }
2470
2471    fn parsed_macro_replacement(
2472        &self,
2473        binding: &MacroBinding,
2474        replacement: &str,
2475    ) -> Arc<ParsedMacroReplacement> {
2476        let key = (binding.source.clone(), binding.declaration_byte);
2477        let mut cache = self
2478            .macro_replacements
2479            .lock()
2480            .expect("C++ macro replacement cache poisoned");
2481        if let Some(parsed) = cache.get(&key) {
2482            return Arc::clone(parsed);
2483        }
2484        #[cfg(any(test, feature = "test-support"))]
2485        self.macro_replacement_parse_count
2486            .fetch_add(1, Ordering::Relaxed);
2487        let source =
2488            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2489        let mut parser = Parser::new();
2490        let parsed = parser
2491            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2492            .ok()
2493            .and_then(|()| parser.parse(&source, None))
2494            .filter(|tree| !tree.root_node().has_error())
2495            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2496                ParsedMacroReplacement::Parsed { source, tree }
2497            });
2498        let parsed = Arc::new(parsed);
2499        cache.insert(key, Arc::clone(&parsed));
2500        parsed
2501    }
2502
2503    /// Recover a typed local declared by an active C function-like macro.
2504    ///
2505    /// This is intentionally narrower than macro expansion. The replacement
2506    /// must parse as one declaration, and the invocation must bind every
2507    /// formal parameter to one structured argument. That is sufficient for
2508    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
2509    /// can make the binding provisional without erasing its last known
2510    /// definition; an explicit conflicting definition still replaces it with
2511    /// Unsupported. Malformed and statement-producing macros also fail closed.
2512    pub fn function_macro_local_binding<'tree>(
2513        &self,
2514        file: &ProjectFile,
2515        statement: Node<'tree>,
2516        source: &str,
2517    ) -> Option<MacroLocalBinding<'tree>> {
2518        if !is_c_source_file(file) {
2519            return None;
2520        }
2521        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2522            return Some(binding);
2523        }
2524        let call = match statement.kind() {
2525            "call_expression" => statement,
2526            "expression_statement" if statement.named_child_count() == 1 => {
2527                statement.named_child(0)?
2528            }
2529            _ => return None,
2530        };
2531        if call.kind() != "call_expression" {
2532            return None;
2533        }
2534        let function = call.child_by_field_name("function")?;
2535        if function.kind() != "identifier" {
2536            return None;
2537        }
2538        let arguments = call.child_by_field_name("arguments")?;
2539        let actuals = argument_children(arguments).collect::<Vec<_>>();
2540        let environment = self.macro_environment(file, call.start_byte());
2541        let function_name = node_text(function, source);
2542        let binding = environment.binding(function_name)?;
2543        let MacroDefinition::Function {
2544            parameters,
2545            replacement,
2546        } = &binding.definition
2547        else {
2548            return None;
2549        };
2550        if actuals.len() != parameters.len() {
2551            return None;
2552        }
2553        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2554        let (type_name, type_node) = match &template.declared_type {
2555            MacroLocalBindingTypeTemplate::Parameter(index) => {
2556                let actual = *actuals.get(*index)?;
2557                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2558                    return None;
2559                }
2560                (node_text(actual, source).trim().to_string(), Some(actual))
2561            }
2562            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2563        };
2564        if type_name.is_empty() {
2565            return None;
2566        }
2567        Some(MacroLocalBinding {
2568            name: template.name.clone(),
2569            type_name,
2570            type_node,
2571            pointer_depth: template.pointer_depth,
2572            proven_unit: None,
2573        })
2574    }
2575
2576    /// Recover the typed receiver established by a C container macro
2577    /// assignment, such as `value = container_of(ptr, struct item, link)`.
2578    ///
2579    /// The macro definition's parsed replacement identifies the one formal
2580    /// parameter used in type position.  The invocation supplies the actual
2581    /// type node, which is then resolved through the ordinary visibility
2582    /// index.  Calls with no unique type-position parameter, an unresolved
2583    /// type, or an uncertain macro environment remain unproven.
2584    pub fn function_macro_container_binding<'tree>(
2585        &self,
2586        analyzer: &CppGraphSource<'_>,
2587        file: &ProjectFile,
2588        assignment: Node<'tree>,
2589        source: &str,
2590    ) -> Option<MacroLocalBinding<'tree>> {
2591        if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2592            return None;
2593        }
2594        let name_node = assignment.child_by_field_name("left")?;
2595        if name_node.kind() != "identifier" {
2596            return None;
2597        }
2598        let call = assignment.child_by_field_name("right")?;
2599        if call.kind() != "call_expression" {
2600            return None;
2601        }
2602        let function = call.child_by_field_name("function")?;
2603        if function.kind() != "identifier" {
2604            return None;
2605        }
2606        let arguments = call.child_by_field_name("arguments")?;
2607        let actuals = argument_children(arguments).collect::<Vec<_>>();
2608        let function_name = node_text(function, source);
2609        let environment = self.macro_environment(file, call.start_byte());
2610        let binding = environment.binding(function_name)?;
2611        if !binding.is_exact() {
2612            return None;
2613        }
2614        let MacroDefinition::Function {
2615            parameters,
2616            replacement,
2617        } = &binding.definition
2618        else {
2619            return None;
2620        };
2621        if actuals.len() != parameters.len() {
2622            return None;
2623        }
2624        let body = self.parsed_macro_replacement_body(
2625            &(binding.source.clone(), binding.declaration_byte),
2626            parameters,
2627            replacement,
2628        )?;
2629        let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2630        let type_argument = *actuals.get(type_parameter)?;
2631        let type_node = macro_type_argument_node(type_argument, source)?;
2632        let type_name = node_text(type_node, source).trim().to_string();
2633        if type_name.is_empty() {
2634            return None;
2635        }
2636        let explicit_tag = match node_text(type_argument, source) {
2637            "struct" => Some(CppCTagKind::Struct),
2638            "union" => Some(CppCTagKind::Union),
2639            _ => None,
2640        };
2641        let resolved_type = if let Some(tag) = explicit_tag {
2642            let candidates = self
2643                .visible_identifier_candidates(file, &type_name)
2644                .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2645                .collect::<Vec<_>>();
2646            self.resolve_type_candidates(
2647                analyzer,
2648                file,
2649                &candidates,
2650                TypeCandidateResolution::Canonical,
2651            )
2652            .ok()
2653        } else {
2654            self.resolve_type_node_result(file, type_node, source)
2655                .ok()
2656                .flatten()
2657        };
2658        let proven_unit = resolved_type?;
2659        Some(MacroLocalBinding {
2660            name: node_text(name_node, source).to_string(),
2661            type_name,
2662            type_node: Some(type_node),
2663            pointer_depth: 1,
2664            proven_unit: Some(proven_unit),
2665        })
2666    }
2667
2668    /// Recover the invocation-specific type of a replacement local visible
2669    /// at the selected source token.
2670    pub fn macro_local_binding_at<'tree>(
2671        &self,
2672        file: &ProjectFile,
2673        root: Node<'tree>,
2674        source: &str,
2675        start_byte: usize,
2676        end_byte: usize,
2677    ) -> Option<MacroLocalBinding<'tree>> {
2678        crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2679    }
2680
2681    /// Resolve the source-backed lexical macro declaration for one source
2682    /// range. The range may be a point range (`start..start + 1`) inside an
2683    /// identifier, which is the form used by goto-definition queries.
2684    pub fn macro_lexical_binding(
2685        &self,
2686        file: &ProjectFile,
2687        root: Node<'_>,
2688        source: &str,
2689        start_byte: usize,
2690        end_byte: usize,
2691    ) -> Option<MacroLexicalBinding> {
2692        crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2693    }
2694
2695    /// Enumerate source ranges whose lexical meaning comes from a
2696    /// function-like macro formal or replacement local. Definition spellings
2697    /// themselves are omitted; callers needing goto-definition on a spelling
2698    /// use [`Self::macro_lexical_binding`].
2699    pub fn macro_lexical_references(
2700        &self,
2701        file: &ProjectFile,
2702        root: Node<'_>,
2703        source: &str,
2704        max_references: usize,
2705        cancelled: impl FnMut() -> bool,
2706    ) -> MacroLexicalReferences {
2707        crate::graph::macro_lexical::all_references(
2708            || self,
2709            file,
2710            root,
2711            source,
2712            max_references,
2713            cancelled,
2714        )
2715    }
2716
2717    /// Return the source identity of the exact active function-like macro
2718    /// binding. The definition AST is hydrated by the lexical helper from
2719    /// this `(ProjectFile, declaration byte)` pair, so no parser node escapes
2720    /// the prepared source borrow.
2721    pub(crate) fn function_macro_binding_at(
2722        &self,
2723        file: &ProjectFile,
2724        name: &str,
2725        before_byte: usize,
2726    ) -> Option<(ProjectFile, usize)> {
2727        let environment = self.macro_environment(file, before_byte);
2728        let binding = environment.binding(name)?;
2729        if binding.is_exact()
2730            && matches!(
2731                binding.definition,
2732                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2733            )
2734        {
2735            return Some((binding.source.clone(), binding.declaration_byte));
2736        }
2737        // A definition under an unknown condition is still certain at a use
2738        // within that same branch. The checkpoint retains the event that made
2739        // the binding uncertain. Require that exact defining event, so an
2740        // intervening undef, include, or conditional redefinition cannot be
2741        // bypassed by a lexical search for an older macro.
2742        if binding.source != *file {
2743            return None;
2744        }
2745        let prepared = self.cpp.prepared_syntax(self.token, file)?;
2746        let root = prepared.tree().root_node();
2747        let source = prepared.source();
2748        let reference = root.descendant_for_byte_range(
2749            before_byte,
2750            before_byte.saturating_add(1).min(source.len()),
2751        )?;
2752        let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2753        let cell = self.macro_event_cell(file);
2754        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2755        let event = events
2756            .iter()
2757            .find(|event| event.byte() == binding.declaration_byte)?;
2758        let MacroEvent::Define {
2759            name: defined_name,
2760            binding: definition,
2761            conditionals,
2762            ..
2763        } = event
2764        else {
2765            return None;
2766        };
2767        if defined_name != name
2768            || conditionals.is_empty()
2769            || !conditionals
2770                .iter()
2771                .all(|condition| reference_conditions.contains(condition))
2772            || !matches!(
2773                definition.definition,
2774                MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2775            )
2776        {
2777            return None;
2778        }
2779        Some((definition.source.clone(), definition.declaration_byte))
2780    }
2781
2782    /// Return the type argument at a C function-like macro invocation.
2783    ///
2784    /// Exact local macro definitions identify type formals from their parsed
2785    /// replacement. An unavailable definition is deliberately not inferred:
2786    /// C has no AST distinction between an external macro call and an
2787    /// ordinary call to an unindexed function.
2788    pub fn function_macro_type_argument<'tree>(
2789        &self,
2790        file: &ProjectFile,
2791        node: Node<'tree>,
2792        source: &str,
2793    ) -> Option<Node<'tree>> {
2794        if !is_c_source_file(file) {
2795            return None;
2796        }
2797        let (call, argument_index, argument) = c_function_macro_argument(node)?;
2798        let function = call.child_by_field_name("function")?;
2799        let function_name = node_text(function, source);
2800        if function_name.is_empty() {
2801            return None;
2802        }
2803        if !self.file_defines_macro_name(file, function_name)
2804            && !self
2805                .visible_identifier_candidates(file, function_name)
2806                .any(|candidate| candidate.is_macro())
2807        {
2808            return None;
2809        }
2810        let environment = self.macro_environment(file, call.start_byte());
2811        let binding = environment.binding(function_name)?;
2812        if !binding.is_exact() {
2813            return None;
2814        }
2815        let (parameters, replacement, variadic) = match &binding.definition {
2816            MacroDefinition::Function {
2817                parameters,
2818                replacement,
2819            } => (parameters, replacement, false),
2820            MacroDefinition::VariadicFunction {
2821                parameters,
2822                replacement,
2823            } => (parameters, replacement, true),
2824            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2825        };
2826        let actuals = call
2827            .child_by_field_name("arguments")
2828            .map(argument_children)
2829            .into_iter()
2830            .flatten()
2831            .collect::<Vec<_>>();
2832        let arity_matches = if variadic {
2833            actuals.len() >= parameters.len()
2834        } else {
2835            actuals.len() == parameters.len()
2836        };
2837        let type_parameters = self.macro_type_parameter_indices(
2838            &(binding.source.clone(), binding.declaration_byte),
2839            parameters,
2840            replacement,
2841        )?;
2842        if !arity_matches || !type_parameters.contains(&argument_index) {
2843            return None;
2844        }
2845        let type_node = macro_type_argument_node(argument, source)?;
2846        if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
2847            return None;
2848        }
2849        Some(type_node)
2850    }
2851
2852    fn macro_local_binding_template(
2853        &self,
2854        binding: &MacroBinding,
2855        parameters: &[String],
2856        replacement: &str,
2857    ) -> Option<Arc<MacroLocalBindingTemplate>> {
2858        let key = (binding.source.clone(), binding.declaration_byte);
2859        if let Some(template) = self
2860            .macro_local_binding_templates
2861            .lock()
2862            .expect("C++ macro local-binding cache poisoned")
2863            .get(&key)
2864        {
2865            return template.clone();
2866        }
2867        let template = (|| {
2868            let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2869            let sentinel = body.source.as_str();
2870            let statements = body.statements()?;
2871            if statements.named_child_count() != 1 {
2872                return None;
2873            }
2874            let declaration = statements.named_child(0)?;
2875            if declaration.kind() != "declaration" {
2876                return None;
2877            }
2878            let type_node = declaration
2879                .child_by_field_name("type")
2880                .or_else(|| first_type_child(declaration))?;
2881            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2882                let mut cursor = declaration.walk();
2883                declaration.named_children(&mut cursor).find_map(|child| {
2884                    if child.kind() == "init_declarator" {
2885                        child.child_by_field_name("declarator")
2886                    } else {
2887                        is_declarator_node(child).then_some(child)
2888                    }
2889                })
2890            })?;
2891            let name = extract_variable_name(declarator, sentinel)?;
2892            let pointer_depth = declared_name_indirection(declaration, type_node, &name, sentinel)?;
2893            let type_text = node_text(type_node, sentinel).trim();
2894            let declared_type = parameters
2895                .iter()
2896                .position(|parameter| parameter == type_text)
2897                .map(MacroLocalBindingTypeTemplate::Parameter)
2898                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2899            Some(Arc::new(MacroLocalBindingTemplate {
2900                name,
2901                declared_type,
2902                pointer_depth,
2903            }))
2904        })();
2905        self.macro_local_binding_templates
2906            .lock()
2907            .expect("C++ macro local-binding cache poisoned")
2908            .insert(key, template.clone());
2909        template
2910    }
2911
2912    /// The parsed replacement body of the function-like macro `definition`
2913    /// defines, or `None` when the replacement cannot be recovered exactly.
2914    ///
2915    /// `definition` is the defining `preproc_function_def` node in `file`, so
2916    /// the result describes that definition rather than whichever same-named
2917    /// macro a later reference resolves to.
2918    pub fn function_macro_replacement_body(
2919        &self,
2920        file: &ProjectFile,
2921        definition: Node<'_>,
2922        source: &str,
2923    ) -> Option<Arc<ParsedReplacementBody>> {
2924        debug_assert_eq!(definition.kind(), "preproc_function_def");
2925        let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
2926            MacroDefinition::Function {
2927                parameters,
2928                replacement,
2929            }
2930            | MacroDefinition::VariadicFunction {
2931                parameters,
2932                replacement,
2933            } => (parameters, replacement),
2934            MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2935        };
2936        self.parsed_macro_replacement_body(
2937            &(file.clone(), definition.start_byte()),
2938            &parameters,
2939            &replacement,
2940        )
2941    }
2942
2943    /// Parse one function-like macro replacement inside the shared sentinel.
2944    ///
2945    /// The parse fails closed, and the failure is cached, whenever the
2946    /// sentinel tree carries an error or the replacement uses preprocessor
2947    /// syntax that has no C++ meaning. Token pasting and stringizing produce
2948    /// `ERROR` nodes; `__VA_ARGS__` parses as an ordinary identifier and is
2949    /// therefore rejected from the parsed tree instead of the source text.
2950    fn parsed_macro_replacement_body(
2951        &self,
2952        key: &(ProjectFile, usize),
2953        parameters: &[String],
2954        replacement: &str,
2955    ) -> Option<Arc<ParsedReplacementBody>> {
2956        if let Some(body) = self
2957            .macro_replacement_bodies
2958            .lock()
2959            .expect("C++ macro replacement body cache poisoned")
2960            .get(key)
2961        {
2962            return body.clone();
2963        }
2964        let body = (|| {
2965            if replacement.trim().is_empty() {
2966                return None;
2967            }
2968            let (source, tree, original_offsets) =
2969                Self::parse_macro_replacement_body(replacement, parameters)?;
2970            let body = ParsedReplacementBody {
2971                source,
2972                tree,
2973                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
2974                parameters: parameters.to_vec(),
2975                original_offsets,
2976            };
2977            body.statements()?;
2978            if body.expands_variadic_arguments() {
2979                return None;
2980            }
2981            Some(Arc::new(body))
2982        })();
2983        self.macro_replacement_bodies
2984            .lock()
2985            .expect("C++ macro replacement body cache poisoned")
2986            .insert(key.clone(), body.clone());
2987        body
2988    }
2989
2990    /// Parse a replacement while preserving a map from the parse buffer back
2991    /// to the original logical-line bytes. A block formal can occur where C
2992    /// requires a statement terminator (`if (condition) block`); the caller's
2993    /// actual block supplies that terminator only after macro substitution.
2994    /// Tree-sitter exposes the formal as an identifier inside recovery, so a
2995    /// semicolon is inserted at that AST-derived boundary for the fallback
2996    /// parse. No identifier text is scanned to find the insertion point.
2997    fn parse_macro_replacement_body(
2998        replacement: &str,
2999        parameters: &[String],
3000    ) -> Option<(String, Tree, Box<[usize]>)> {
3001        let normalized = normalize_macro_continuations(replacement);
3002        let parse = |replacement: &str| {
3003            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3004            let mut parser = Parser::new();
3005            parser
3006                .set_language(&tree_sitter_cpp::LANGUAGE.into())
3007                .ok()?;
3008            let tree = parser.parse(&source, None)?;
3009            Some((source, tree))
3010        };
3011        let (source, tree) = parse(&normalized)?;
3012        if !tree.root_node().has_error() {
3013            let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
3014            Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3015            return Some((source, tree, original_offsets.into_boxed_slice()));
3016        }
3017
3018        let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3019        let mut insertion_points = Vec::new();
3020        let mut stack = vec![tree.root_node()];
3021        while let Some(node) = stack.pop() {
3022            if matches!(
3023                node.kind(),
3024                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3025            ) && parameters
3026                .iter()
3027                .any(|parameter| parameter.as_str() == node_text(node, &source))
3028                && Self::macro_formal_needs_statement_separator(node)
3029            {
3030                let point = node.end_byte().saturating_sub(body_offset);
3031                if point <= normalized.len() && !insertion_points.contains(&point) {
3032                    insertion_points.push(point);
3033                }
3034            }
3035            push_named_children_reversed(node, &mut stack);
3036        }
3037        if insertion_points.is_empty() {
3038            return None;
3039        }
3040        insertion_points.sort_unstable();
3041        let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3042        let mut original_offsets =
3043            Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3044        let mut next_insertion = 0;
3045        for (index, byte) in normalized.bytes().enumerate() {
3046            recovered.push(byte);
3047            original_offsets.push(index);
3048            while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3049                recovered.push(b';');
3050                original_offsets.push(index + 1);
3051                next_insertion += 1;
3052            }
3053        }
3054        original_offsets.push(normalized.len());
3055        Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3056        let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3057        let (source, tree) = parse(&recovered)?;
3058        if tree.root_node().has_error() {
3059            return None;
3060        }
3061        Some((source, tree, original_offsets.into_boxed_slice()))
3062    }
3063
3064    /// Extend a replacement-origin map over the `; }` suffix appended by the
3065    /// sentinel parser. Nodes such as a declaration whose source omits its
3066    /// terminator include that synthetic semicolon; every suffix boundary
3067    /// still maps to the replacement's real end.
3068    fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3069        offsets.extend([replacement_end; 3]);
3070    }
3071
3072    /// A formal is statement-shaped when the recovered tree places it directly
3073    /// at a statement boundary. This is the grammar recovery produced for
3074    /// replacement forms such as `if (condition) block`; an identifier inside
3075    /// an expression or call argument must not receive an artificial `;`.
3076    fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3077        let mut current = node;
3078        let mut crossed_recovery = false;
3079        while let Some(parent) = current.parent() {
3080            if parent.is_error() {
3081                crossed_recovery = true;
3082                current = parent;
3083                continue;
3084            }
3085            if matches!(
3086                parent.kind(),
3087                "call_expression"
3088                    | "argument_list"
3089                    | "field_expression"
3090                    | "binary_expression"
3091                    | "unary_expression"
3092                    | "assignment_expression"
3093                    | "conditional_expression"
3094                    | "parenthesized_expression"
3095                    | "subscript_expression"
3096            ) {
3097                return false;
3098            }
3099            if parent.kind() == "expression_statement" {
3100                return parent.named_child_count() == 1;
3101            }
3102            if parent.kind() == "compound_statement" && current == node {
3103                return true;
3104            }
3105            if matches!(
3106                parent.kind(),
3107                "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3108            ) {
3109                return crossed_recovery
3110                    || parent.child_by_field_name("consequence") == Some(current);
3111            }
3112            if matches!(parent.kind(), "declaration" | "init_declarator") {
3113                return false;
3114            }
3115            current = parent;
3116        }
3117        false
3118    }
3119
3120    /// Identify fixed macro formals that retain an unambiguous type role even
3121    /// when an unrelated part of the replacement needs parser recovery.
3122    ///
3123    /// Variadic production macros commonly contain token pasting elsewhere in
3124    /// the body. The strict shared replacement parser must keep rejecting that
3125    /// body for expansion modeling, but a type declaration such as `TY col`
3126    /// remains usable when the node itself retains a structured type role.
3127    fn macro_type_parameter_indices(
3128        &self,
3129        key: &(ProjectFile, usize),
3130        parameters: &[String],
3131        replacement: &str,
3132    ) -> Option<Arc<[usize]>> {
3133        if let Some(indices) = self
3134            .macro_type_parameters
3135            .lock()
3136            .expect("C++ macro type-parameter cache poisoned")
3137            .get(key)
3138        {
3139            return indices.clone();
3140        }
3141        #[cfg(any(test, feature = "test-support"))]
3142        self.macro_replacement_parse_count
3143            .fetch_add(1, Ordering::Relaxed);
3144        let indices = (|| {
3145            if replacement.trim().is_empty() {
3146                return None;
3147            }
3148            // Keep this parser permissive. Type-role extraction predates the
3149            // replacement-body model and deliberately survives unrelated
3150            // token-pasting or recovery elsewhere in a production macro.
3151            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3152            let mut parser = Parser::new();
3153            parser
3154                .set_language(&tree_sitter_cpp::LANGUAGE.into())
3155                .ok()?;
3156            let tree = parser.parse(&source, None)?;
3157            let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3158            Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3159            let body = ParsedReplacementBody {
3160                source,
3161                tree,
3162                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3163                parameters: parameters.to_vec(),
3164                original_offsets: original_offsets.into_boxed_slice(),
3165            };
3166            macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3167        })();
3168        self.macro_type_parameters
3169            .lock()
3170            .expect("C++ macro type-parameter cache poisoned")
3171            .insert(key.clone(), indices.clone());
3172        indices
3173    }
3174
3175    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3176        let replacement = if node.kind() == "preproc_function_def" {
3177            function_macro_replacement_span(node, source)
3178                .and_then(|span| source.get(span))
3179                .map(str::to_owned)
3180                .or_else(|| {
3181                    node.child_by_field_name("value")
3182                        .map(|value| node_text(value, source).to_string())
3183                })
3184                .unwrap_or_default()
3185        } else {
3186            node.child_by_field_name("value")
3187                .map(|value| node_text(value, source).to_string())
3188                .unwrap_or_default()
3189        };
3190        if node.kind() == "preproc_def" {
3191            return MacroDefinition::Object { replacement };
3192        }
3193        let Some(parameters) = node.child_by_field_name("parameters") else {
3194            return MacroDefinition::Unsupported;
3195        };
3196        let variadic = (0..parameters.child_count()).any(|index| {
3197            parameters
3198                .child(index)
3199                .is_some_and(|child| child.kind() == "...")
3200        });
3201        let parameters = (0..parameters.named_child_count())
3202            .filter_map(|index| parameters.named_child(index))
3203            .map(|parameter| node_text(parameter, source).to_string())
3204            .collect::<Vec<_>>();
3205        if variadic {
3206            MacroDefinition::VariadicFunction {
3207                parameters,
3208                replacement,
3209            }
3210        } else {
3211            MacroDefinition::Function {
3212                parameters,
3213                replacement,
3214            }
3215        }
3216    }
3217
3218    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3219        self.macro_event_cells
3220            .lock()
3221            .expect("C++ macro event cache poisoned")
3222            .entry(file.clone())
3223            .or_default()
3224            .clone()
3225    }
3226
3227    fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3228        if let Some(names) = self
3229            .macro_event_name_sets
3230            .lock()
3231            .expect("C++ macro event-name cache poisoned")
3232            .get(file)
3233            .cloned()
3234        {
3235            return names.contains(name);
3236        }
3237        let cell = self.macro_event_cell(file);
3238        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3239        let names = Arc::new(
3240            events
3241                .iter()
3242                .filter_map(|event| match event {
3243                    MacroEvent::Define { name, .. } => Some(name.clone()),
3244                    MacroEvent::Undef { .. }
3245                    | MacroEvent::Include { .. }
3246                    | MacroEvent::Invalidate { .. } => None,
3247                })
3248                .collect(),
3249        );
3250        self.macro_event_name_sets
3251            .lock()
3252            .expect("C++ macro event-name cache poisoned")
3253            .insert(file.clone(), Arc::clone(&names));
3254        names.contains(name)
3255    }
3256
3257    fn macro_environment_checkpoint_cell(
3258        &self,
3259        file: &ProjectFile,
3260    ) -> MacroEnvironmentCheckpointCell {
3261        self.macro_environment_checkpoints
3262            .lock()
3263            .expect("C++ macro environment checkpoint cache poisoned")
3264            .entry(file.clone())
3265            .or_default()
3266            .clone()
3267    }
3268
3269    /// The environment of `file`'s macro events applied up to `before_byte`.
3270    pub fn macro_environment(
3271        &self,
3272        file: &ProjectFile,
3273        before_byte: usize,
3274    ) -> Arc<MacroEnvironment> {
3275        #[cfg(any(test, feature = "test-support"))]
3276        self.macro_environment_request_count
3277            .fetch_add(1, Ordering::Relaxed);
3278        let cell = self.macro_event_cell(file);
3279        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3280        let frontier = events.partition_point(|event| event.byte() < before_byte);
3281        let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3282        let checkpoints =
3283            checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3284        let checkpoint = checkpoints.at_or_before(frontier);
3285        if checkpoint.frontier == frontier {
3286            return Arc::clone(&checkpoint.environment);
3287        }
3288        #[cfg(any(test, feature = "test-support"))]
3289        self.macro_environment_copy_count
3290            .fetch_add(1, Ordering::Relaxed);
3291        let mut environment = checkpoint.environment.as_ref().clone();
3292        let mut include_stack = HashSet::from_iter([file.clone()]);
3293        for event in &events[checkpoint.frontier..frontier] {
3294            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3295        }
3296        Arc::new(environment)
3297    }
3298
3299    /// Apply `file`'s events once, keeping the environment at the prefixes
3300    /// [`MacroEnvironmentCheckpoints`] describes.
3301    fn build_macro_environment_checkpoints(
3302        &self,
3303        file: &ProjectFile,
3304        events: &[MacroEvent],
3305    ) -> MacroEnvironmentCheckpoints {
3306        #[cfg(any(test, feature = "test-support"))]
3307        self.macro_environment_checkpoint_build_count
3308            .fetch_add(1, Ordering::Relaxed);
3309        // The TU's build-proven defines hold from the first byte (#2011): they
3310        // are facts of the whole compile, so they seed the frontier-zero
3311        // checkpoint. A later explicit #undef event still overrides them
3312        // through `known_undefined_names`.
3313        let mut environment = MacroEnvironment {
3314            build_proven_defines: self
3315                .compile_proven_guards(file)
3316                .iter()
3317                .filter_map(|guard| match guard {
3318                    PreprocessorGuard::Defined(name) => Some(name.clone()),
3319                    _ => None,
3320                })
3321                .collect(),
3322            ..MacroEnvironment::default()
3323        };
3324        let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3325            frontier: 0,
3326            environment: Arc::new(environment.clone()),
3327        }];
3328        // Keep roughly one fixed stride's worth of checkpoints, while taking
3329        // a checkpoint at every event for event sets no larger than the fixed
3330        // stride. Generated C tables commonly have thousands of uses between
3331        // one #define and a trailing #undef; with a fixed stride those
3332        // identical frontier requests would all copy and replay the same
3333        // prefix.
3334        let checkpoint_stride = events
3335            .len()
3336            .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3337            .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3338        let mut include_stack = HashSet::from_iter([file.clone()]);
3339        for (index, event) in events.iter().enumerate() {
3340            self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3341            let frontier = index + 1;
3342            if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3343                checkpoints.push(MacroEnvironmentCheckpoint {
3344                    frontier,
3345                    environment: Arc::new(environment.clone()),
3346                });
3347            }
3348        }
3349        MacroEnvironmentCheckpoints { checkpoints }
3350    }
3351
3352    /// Whether `name` is bound as a macro at `before_byte` in `file`,
3353    /// including a binding this environment cannot pin to one replacement
3354    /// (a conditional `#define`, or a function-like macro).
3355    ///
3356    /// [`Self::object_macro_replacement_at`] collapses every such binding to
3357    /// `None`, which is indistinguishable from "not a macro at all". A caller
3358    /// that must not read a macro token as an ordinary type name needs the two
3359    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
3360    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3361        self.macro_environment(file, before_byte)
3362            .binding(name)
3363            .is_some()
3364    }
3365
3366    pub fn macro_name_may_be_bound_at(
3367        &self,
3368        file: &ProjectFile,
3369        name: &str,
3370        before_byte: usize,
3371    ) -> bool {
3372        self.macro_environment(file, before_byte).may_bind(name)
3373    }
3374
3375    /// Whether the active macro binding at this reference is the requested
3376    /// indexed definition. Name equality alone is not enough because two
3377    /// headers can define the same macro for different translation units.
3378    pub fn macro_binding_matches_target_at(
3379        &self,
3380        analyzer: &CppGraphSource<'_>,
3381        file: &ProjectFile,
3382        name: &str,
3383        before_byte: usize,
3384        target: &CodeUnit,
3385    ) -> bool {
3386        let ranges = analyzer.ranges(target);
3387        let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3388        self.macro_binding_matches_target_declaration_at(
3389            file,
3390            name,
3391            before_byte,
3392            target.source(),
3393            &declaration_bytes,
3394        )
3395    }
3396
3397    /// Resolve declaration ranges once for inverse scans that test many call
3398    /// sites against one macro target. Walking a large generated syntax tree
3399    /// back to the same `#define` for every call site is otherwise quadratic
3400    /// in the number of top-level declarations.
3401    pub(crate) fn macro_declaration_bytes(
3402        &self,
3403        target: &CodeUnit,
3404        ranges: &[Range],
3405    ) -> Vec<usize> {
3406        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3407            return Vec::new();
3408        };
3409        ranges
3410            .iter()
3411            .filter_map(|range| {
3412                let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3413                while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3414                    node = node.parent()?;
3415                }
3416                Some(node.start_byte())
3417            })
3418            .collect()
3419    }
3420
3421    pub(crate) fn macro_binding_matches_target_declaration_at(
3422        &self,
3423        file: &ProjectFile,
3424        name: &str,
3425        before_byte: usize,
3426        target_source: &ProjectFile,
3427        target_declaration_bytes: &[usize],
3428    ) -> bool {
3429        let environment = self.macro_environment(file, before_byte);
3430        let Some(binding) = environment.binding(name) else {
3431            return false;
3432        };
3433        if binding.definition == MacroDefinition::Unsupported {
3434            return false;
3435        }
3436        // A normal header guard makes the replacement text conditional, but
3437        // it does not erase the definition site's source and byte identity.
3438        // Keep that identity even when expansion details are not exact.
3439        if binding.source != *target_source {
3440            return false;
3441        }
3442        target_declaration_bytes.contains(&binding.declaration_byte)
3443    }
3444
3445    /// Resolve an ordinary expression-position macro token at its exact byte.
3446    ///
3447    /// Calls and preprocessor-condition tokens have separate resolution
3448    /// surfaces. Declaration names, macro parameters, and labels are not
3449    /// references. Keeping that role policy here makes forward and both
3450    /// inverse graph builders consume the same activation verdict (#2093).
3451    pub fn resolve_ordinary_macro_reference(
3452        &self,
3453        analyzer: &CppGraphSource<'_>,
3454        file: &ProjectFile,
3455        node: Node<'_>,
3456        source: &str,
3457    ) -> OrdinaryMacroReferenceResolution {
3458        if !is_ordinary_macro_reference_node(node) {
3459            return OrdinaryMacroReferenceResolution::Missing;
3460        }
3461        let name = node_text(node, source);
3462        if name.is_empty() {
3463            return OrdinaryMacroReferenceResolution::Missing;
3464        }
3465        let visible = self
3466            .visible_identifier_candidates(file, name)
3467            .filter(|candidate| candidate.is_macro())
3468            .cloned()
3469            .collect::<Vec<_>>();
3470        let mut exact = Vec::new();
3471        for candidate in &visible {
3472            if self.macro_binding_matches_target_at(
3473                analyzer,
3474                file,
3475                name,
3476                node.start_byte(),
3477                candidate,
3478            ) && !exact
3479                .iter()
3480                .any(|existing| same_visible_symbol(existing, candidate))
3481            {
3482                exact.push(candidate.clone());
3483            }
3484        }
3485        match exact.len() {
3486            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3487            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3488            0 if !visible.is_empty()
3489                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3490            {
3491                OrdinaryMacroReferenceResolution::Ambiguous
3492            }
3493            0 => OrdinaryMacroReferenceResolution::Missing,
3494        }
3495    }
3496
3497    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
3498    ///
3499    /// The ordinary census deliberately skips every `ERROR` subtree. This
3500    /// separate, precision-only frontier admits only roles that retain enough
3501    /// structure for the C usage graph to interpret independently (#2089).
3502    /// Macro evidence comes from this visibility index at the exact byte; no
3503    /// source-text parsing or terminal-name fallback is used.
3504    pub fn recovered_c_reference_ranges(
3505        &self,
3506        file: &ProjectFile,
3507        root: Node<'_>,
3508        source: &str,
3509        limit: usize,
3510    ) -> RecoveredCReferenceRanges {
3511        if !is_c_source_file(file) {
3512            return RecoveredCReferenceRanges::Complete(Vec::new());
3513        }
3514        let mut ranges = Vec::new();
3515        let mut seen = HashSet::default();
3516        let mut stack = vec![(root, root.is_error())];
3517        while let Some((node, inside_error)) = stack.pop() {
3518            let inside_error = inside_error || node.is_error();
3519            if node.kind() == "preproc_arg" {
3520                // Tree-sitter keeps an object-like replacement opaque. The
3521                // inverse extractor uses the same parsed replacement helper;
3522                // retain its exact macro/type leaves for precision membership
3523                // even when the preprocessor node is outside ERROR recovery.
3524                let macro_value_kind = node.parent().and_then(|parent| {
3525                    (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3526                });
3527                if matches!(
3528                    macro_value_kind,
3529                    Some("preproc_def" | "preproc_function_def")
3530                ) {
3531                    let name = node_text(node, source);
3532                    if !name.is_empty()
3533                        && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3534                        && !push_recovered_c_range(
3535                            &mut ranges,
3536                            &mut seen,
3537                            node.start_byte(),
3538                            node.end_byte(),
3539                            node,
3540                            limit,
3541                        )
3542                    {
3543                        return RecoveredCReferenceRanges::LimitExceeded;
3544                    }
3545                }
3546                if macro_value_kind == Some("preproc_def") {
3547                    for reference in object_macro_replacement_type_references(node, source) {
3548                        for range in reference.component_ranges {
3549                            let visible = self
3550                                .visible_identifier_candidates(file, &source[range.clone()])
3551                                .any(|candidate| {
3552                                    candidate.is_class()
3553                                        || candidate.is_module()
3554                                        || is_type_alias(candidate)
3555                                });
3556                            if visible
3557                                && !push_recovered_c_range(
3558                                    &mut ranges,
3559                                    &mut seen,
3560                                    range.start,
3561                                    range.end,
3562                                    node,
3563                                    limit,
3564                                )
3565                            {
3566                                return RecoveredCReferenceRanges::LimitExceeded;
3567                            }
3568                        }
3569                    }
3570                }
3571            }
3572            if inside_error
3573                && recovered_c_reference_node(self, file, node, source)
3574                && !push_recovered_c_range(
3575                    &mut ranges,
3576                    &mut seen,
3577                    node.start_byte(),
3578                    node.end_byte(),
3579                    node,
3580                    limit,
3581                )
3582            {
3583                return RecoveredCReferenceRanges::LimitExceeded;
3584            }
3585            let mut cursor = node.walk();
3586            for child in node.named_children(&mut cursor) {
3587                stack.push((child, inside_error));
3588            }
3589        }
3590        ranges.sort_unstable();
3591        RecoveredCReferenceRanges::Complete(ranges)
3592    }
3593
3594    /// Whether this target is an indexed macro visible from this file.
3595    ///
3596    /// An unresolved conditional can make more than one same-name macro a
3597    /// possible active binding. Each possible target can keep the site as an
3598    /// unproven hit. A macro in an unrelated translation unit stays excluded.
3599    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3600        self.visible_identifier_candidates(file, target.identifier())
3601            .filter(|candidate| candidate.is_macro())
3602            .any(|candidate| {
3603                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3604            })
3605    }
3606
3607    pub fn object_macro_replacement_at(
3608        &self,
3609        file: &ProjectFile,
3610        name: &str,
3611        before_byte: usize,
3612    ) -> Option<String> {
3613        let environment = self.macro_environment(file, before_byte);
3614        let binding = environment.binding(name)?;
3615        if !binding.exact {
3616            return None;
3617        }
3618        match &binding.definition {
3619            MacroDefinition::Object { replacement } => Some(replacement.clone()),
3620            MacroDefinition::Function { .. }
3621            | MacroDefinition::VariadicFunction { .. }
3622            | MacroDefinition::Unsupported => None,
3623        }
3624    }
3625
3626    fn apply_macro_events(
3627        &self,
3628        file: &ProjectFile,
3629        before_byte: Option<usize>,
3630        environment: &mut MacroEnvironment,
3631        include_stack: &mut HashSet<ProjectFile>,
3632    ) {
3633        if !include_stack.insert(file.clone()) {
3634            return;
3635        }
3636        if self.cpp.prepared_syntax(self.token, file).is_none() {
3637            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3638            include_stack.remove(file);
3639            return;
3640        }
3641        match self.macro_include_protection(file) {
3642            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3643                Some(binding) if binding.is_exact() => {
3644                    include_stack.remove(file);
3645                    return;
3646                }
3647                Some(_) | None if environment.unknown_names => {
3648                    let mut ambiguous_seen = HashSet::default();
3649                    self.mark_macro_events_ambiguous(
3650                        file,
3651                        environment,
3652                        &mut ambiguous_seen,
3653                        file,
3654                        before_byte.unwrap_or_default(),
3655                    );
3656                    include_stack.remove(file);
3657                    return;
3658                }
3659                Some(_) => {
3660                    let mut ambiguous_seen = HashSet::default();
3661                    self.mark_macro_events_ambiguous(
3662                        file,
3663                        environment,
3664                        &mut ambiguous_seen,
3665                        file,
3666                        before_byte.unwrap_or_default(),
3667                    );
3668                    include_stack.remove(file);
3669                    return;
3670                }
3671                None => {}
3672            },
3673            MacroIncludeProtection::PragmaOnce => {
3674                if !environment.applied_pragma_once_files.insert(file.clone()) {
3675                    include_stack.remove(file);
3676                    return;
3677                }
3678                if environment.maybe_applied_pragma_once_files.remove(file) {
3679                    // A prior conditional include may already have consumed the pragma-once
3680                    // header. This unconditional include guarantees it is consumed now, but
3681                    // cannot prove whether its events occur before or after intervening local
3682                    // macro changes, so preserve the union as ambiguous.
3683                    let mut ambiguous_seen = HashSet::default();
3684                    environment.applied_pragma_once_files.remove(file);
3685                    self.mark_macro_events_ambiguous(
3686                        file,
3687                        environment,
3688                        &mut ambiguous_seen,
3689                        file,
3690                        before_byte.unwrap_or_default(),
3691                    );
3692                    environment.maybe_applied_pragma_once_files.remove(file);
3693                    environment.applied_pragma_once_files.insert(file.clone());
3694                    include_stack.remove(file);
3695                    return;
3696                }
3697            }
3698            MacroIncludeProtection::None => {}
3699        }
3700        let cell = self.macro_event_cell(file);
3701        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3702        for event in events {
3703            if before_byte.is_some_and(|limit| event.byte() >= limit) {
3704                break;
3705            }
3706            self.apply_macro_event(file, event, environment, include_stack);
3707        }
3708        include_stack.remove(file);
3709    }
3710
3711    fn apply_macro_event(
3712        &self,
3713        file: &ProjectFile,
3714        event: &MacroEvent,
3715        environment: &mut MacroEnvironment,
3716        include_stack: &mut HashSet<ProjectFile>,
3717    ) {
3718        #[cfg(any(test, feature = "test-support"))]
3719        self.macro_event_application_count
3720            .fetch_add(1, Ordering::Relaxed);
3721        match event {
3722            MacroEvent::Define {
3723                name,
3724                binding,
3725                conditionals,
3726                byte,
3727            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3728                Some(true) => environment.insert(name.clone(), binding.clone()),
3729                Some(false) => {}
3730                None => Self::merge_conditional_macro_definition(
3731                    environment,
3732                    name,
3733                    binding,
3734                    file,
3735                    *byte,
3736                ),
3737            },
3738            MacroEvent::Undef {
3739                name,
3740                conditionals,
3741                byte,
3742            } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3743                Some(true) => environment.remove(name),
3744                Some(false) => {}
3745                None => {
3746                    if environment.binding(name).is_some() {
3747                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3748                    }
3749                }
3750            },
3751            MacroEvent::Include {
3752                targets,
3753                conditionals,
3754                byte,
3755            } => {
3756                let condition =
3757                    self.macro_event_condition_value(file, *byte, environment, conditionals);
3758                if condition == Some(false) {
3759                    return;
3760                }
3761                if targets.is_empty() {
3762                    environment.mark_unknown_names(file, *byte);
3763                    return;
3764                }
3765                if condition.is_none() || targets.len() > 1 {
3766                    let mut ambiguous_seen = HashSet::default();
3767                    for target in targets {
3768                        self.mark_macro_events_ambiguous(
3769                            target,
3770                            environment,
3771                            &mut ambiguous_seen,
3772                            file,
3773                            *byte,
3774                        );
3775                    }
3776                } else if let Some(target) = targets.first() {
3777                    self.apply_macro_events(target, None, environment, include_stack);
3778                }
3779            }
3780            MacroEvent::Invalidate { byte } => {
3781                for binding in environment.bindings.values_mut() {
3782                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
3783                }
3784            }
3785        }
3786    }
3787
3788    /// Evaluate the structured conditional path that owns one macro event.
3789    ///
3790    /// `Some(true)` and `Some(false)` are proofs from exact macro bindings at
3791    /// this source byte. `None` preserves the old conditional merge when a
3792    /// build/configuration input or an unsupported expression is involved.
3793    ///
3794    /// `conditionals` is the event's own [`OwningPreprocessorConditionals`]:
3795    /// which ancestors structurally own the event was decided when the events
3796    /// were collected, so all this walk does is find those nodes again and ask
3797    /// the environment what their conditions are worth here.
3798    fn macro_event_condition_value(
3799        &self,
3800        file: &ProjectFile,
3801        event_byte: usize,
3802        environment: &MacroEnvironment,
3803        conditionals: &OwningPreprocessorConditionals,
3804    ) -> Option<bool> {
3805        if conditionals.is_empty() {
3806            return Some(true);
3807        }
3808        let prepared = self.cpp.prepared_syntax(self.token, file)?;
3809        let source = prepared.source();
3810        let root = prepared.tree().root_node();
3811        let descendant = root.descendant_for_byte_range(
3812            event_byte,
3813            event_byte.saturating_add(1).min(source.len()),
3814        )?;
3815        let mut unknown = false;
3816        let mut current = descendant.parent();
3817        while let Some(conditional) = current {
3818            if matches!(
3819                conditional.kind(),
3820                "preproc_if" | "preproc_ifdef" | "preproc_elif"
3821            ) && conditionals.contains(&conditional.start_byte())
3822            {
3823                let mut value = match conditional.kind() {
3824                    "preproc_ifdef" => {
3825                        let name = conditional.child_by_field_name("name")?;
3826                        let defined =
3827                            self.macro_name_defined_value(environment, node_text(name, source));
3828                        match conditional.child(0)?.kind() {
3829                            "#ifdef" => defined,
3830                            "#ifndef" => defined.map(|defined| !defined),
3831                            _ => None,
3832                        }
3833                    }
3834                    "preproc_if" | "preproc_elif" => conditional
3835                        .child_by_field_name("condition")
3836                        .and_then(|condition| {
3837                            self.preprocessor_integer_value(
3838                                condition,
3839                                source,
3840                                environment,
3841                                &mut Vec::new(),
3842                                0,
3843                            )
3844                        })
3845                        .map(|value| value != 0),
3846                    _ => unreachable!(),
3847                };
3848                if conditional
3849                    .child_by_field_name("alternative")
3850                    .is_some_and(|alternative| {
3851                        alternative.start_byte() <= descendant.start_byte()
3852                            && descendant.end_byte() <= alternative.end_byte()
3853                    })
3854                {
3855                    value = value.map(|value| !value);
3856                }
3857                match value {
3858                    Some(true) => {}
3859                    Some(false) => return Some(false),
3860                    None => unknown = true,
3861                }
3862            }
3863            current = conditional.parent();
3864        }
3865        (!unknown).then_some(true)
3866    }
3867
3868    fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
3869        if environment.known_undefined_names.contains(name) {
3870            return Some(false);
3871        }
3872        if let Some(binding) = environment.binding(name) {
3873            return binding.is_exact().then_some(true);
3874        }
3875        environment
3876            .build_proven_defines
3877            .contains(name)
3878            .then_some(true)
3879    }
3880
3881    fn preprocessor_integer_value(
3882        &self,
3883        expression: Node<'_>,
3884        source: &str,
3885        environment: &MacroEnvironment,
3886        expansion_stack: &mut Vec<(ProjectFile, usize)>,
3887        depth: usize,
3888    ) -> Option<i128> {
3889        // Macro replacement graphs can cycle. This explicit bound makes the
3890        // otherwise recursive AST evaluation stack-safe for hostile input.
3891        if depth >= 64 {
3892            return None;
3893        }
3894        match expression.kind() {
3895            "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
3896            "identifier" | "type_identifier" => {
3897                let binding = environment.binding(node_text(expression, source))?;
3898                if !binding.is_exact() {
3899                    return None;
3900                }
3901                let MacroDefinition::Object { replacement } = &binding.definition else {
3902                    return None;
3903                };
3904                let identity = (binding.source.clone(), binding.declaration_byte);
3905                if expansion_stack.contains(&identity) {
3906                    return None;
3907                }
3908                expansion_stack.push(identity);
3909                let parsed = self.parsed_macro_replacement(binding, replacement);
3910                let value = match parsed.as_ref() {
3911                    ParsedMacroReplacement::Parsed {
3912                        source: replacement_source,
3913                        tree,
3914                    } => first_descendant_of_kind(tree.root_node(), "call_expression")
3915                        .and_then(|call| call.child_by_field_name("arguments"))
3916                        .and_then(|arguments| argument_children(arguments).next())
3917                        .and_then(|argument| {
3918                            self.preprocessor_integer_value(
3919                                argument,
3920                                replacement_source,
3921                                environment,
3922                                expansion_stack,
3923                                depth + 1,
3924                            )
3925                        }),
3926                    ParsedMacroReplacement::Unsupported => None,
3927                };
3928                expansion_stack.pop();
3929                value
3930            }
3931            "preproc_defined" => {
3932                let mut cursor = expression.walk();
3933                let name = expression
3934                    .named_children(&mut cursor)
3935                    .find(|child| child.kind() == "identifier")?;
3936                self.macro_name_defined_value(environment, node_text(name, source))
3937                    .map(i128::from)
3938            }
3939            "parenthesized_expression" => expression.named_child(0).and_then(|child| {
3940                self.preprocessor_integer_value(
3941                    child,
3942                    source,
3943                    environment,
3944                    expansion_stack,
3945                    depth + 1,
3946                )
3947            }),
3948            "unary_expression" => {
3949                let operator = expression.child_by_field_name("operator")?.kind();
3950                let argument = expression.child_by_field_name("argument")?;
3951                let value = self.preprocessor_integer_value(
3952                    argument,
3953                    source,
3954                    environment,
3955                    expansion_stack,
3956                    depth + 1,
3957                )?;
3958                match operator {
3959                    "+" => Some(value),
3960                    "-" => value.checked_neg(),
3961                    "!" => Some(i128::from(value == 0)),
3962                    "~" => Some(!value),
3963                    _ => None,
3964                }
3965            }
3966            "binary_expression" => {
3967                let left = self.preprocessor_integer_value(
3968                    expression.child_by_field_name("left")?,
3969                    source,
3970                    environment,
3971                    expansion_stack,
3972                    depth + 1,
3973                )?;
3974                let right = self.preprocessor_integer_value(
3975                    expression.child_by_field_name("right")?,
3976                    source,
3977                    environment,
3978                    expansion_stack,
3979                    depth + 1,
3980                )?;
3981                match expression.child_by_field_name("operator")?.kind() {
3982                    "+" => left.checked_add(right),
3983                    "-" => left.checked_sub(right),
3984                    "*" => left.checked_mul(right),
3985                    "/" => left.checked_div(right),
3986                    "%" => left.checked_rem(right),
3987                    "<<" => u32::try_from(right)
3988                        .ok()
3989                        .and_then(|shift| left.checked_shl(shift)),
3990                    ">>" => u32::try_from(right)
3991                        .ok()
3992                        .and_then(|shift| left.checked_shr(shift)),
3993                    "<" => Some(i128::from(left < right)),
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(left & right),
4000                    "|" => Some(left | right),
4001                    "^" => Some(left ^ right),
4002                    "&&" => Some(i128::from(left != 0 && right != 0)),
4003                    "||" => Some(i128::from(left != 0 || right != 0)),
4004                    _ => None,
4005                }
4006            }
4007            _ => None,
4008        }
4009    }
4010
4011    fn mark_macro_events_ambiguous(
4012        &self,
4013        file: &ProjectFile,
4014        environment: &mut MacroEnvironment,
4015        include_stack: &mut HashSet<ProjectFile>,
4016        conditional_file: &ProjectFile,
4017        conditional_byte: usize,
4018    ) {
4019        if !include_stack.insert(file.clone()) {
4020            return;
4021        }
4022        if self.cpp.prepared_syntax(self.token, file).is_none() {
4023            environment.mark_unknown_names(conditional_file, conditional_byte);
4024            return;
4025        }
4026        match self.macro_include_protection(file) {
4027            MacroIncludeProtection::MacroGuard(guard) => {
4028                if environment
4029                    .binding(&guard)
4030                    .is_some_and(MacroBinding::is_exact)
4031                {
4032                    return;
4033                }
4034            }
4035            MacroIncludeProtection::PragmaOnce => {
4036                if environment.applied_pragma_once_files.contains(file) {
4037                    return;
4038                }
4039                environment
4040                    .maybe_applied_pragma_once_files
4041                    .insert(file.clone());
4042            }
4043            MacroIncludeProtection::None => {}
4044        }
4045        let cell = self.macro_event_cell(file);
4046        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4047        for event in events {
4048            #[cfg(any(test, feature = "test-support"))]
4049            self.macro_event_application_count
4050                .fetch_add(1, Ordering::Relaxed);
4051            match event {
4052                MacroEvent::Define { name, binding, .. } => {
4053                    Self::merge_conditional_macro_definition(
4054                        environment,
4055                        name,
4056                        binding,
4057                        conditional_file,
4058                        conditional_byte,
4059                    );
4060                }
4061                MacroEvent::Undef { name, .. } => {
4062                    if environment.binding(name).is_some() {
4063                        environment.insert(
4064                            name.clone(),
4065                            MacroBinding::ambiguous(conditional_file, conditional_byte),
4066                        );
4067                    } else {
4068                        environment.remove_known_undefined(name);
4069                    }
4070                }
4071                MacroEvent::Include { targets, .. } => {
4072                    if targets.is_empty() {
4073                        environment.mark_unknown_names(conditional_file, conditional_byte);
4074                        continue;
4075                    }
4076                    for target in targets {
4077                        self.mark_macro_events_ambiguous(
4078                            target,
4079                            environment,
4080                            include_stack,
4081                            conditional_file,
4082                            conditional_byte,
4083                        );
4084                    }
4085                }
4086                MacroEvent::Invalidate { .. } => {
4087                    for binding in environment.bindings.values_mut() {
4088                        *binding = MacroBinding::uncertain_from(
4089                            binding,
4090                            conditional_file,
4091                            conditional_byte,
4092                        );
4093                    }
4094                }
4095            }
4096        }
4097    }
4098
4099    fn merge_conditional_macro_definition(
4100        environment: &mut MacroEnvironment,
4101        name: &str,
4102        possible_binding: &MacroBinding,
4103        conditional_file: &ProjectFile,
4104        conditional_byte: usize,
4105    ) {
4106        // A conditional include can revisit an already-active guarded header.
4107        // If the possible branch defines the exact same macro, both outcomes
4108        // leave the binding unchanged; degrading it to Unknown would discard
4109        // proof because of an unrelated unresolved macro name (#2092).
4110        if environment.binding(name).is_some_and(|current| {
4111            current.definition != MacroDefinition::Unsupported
4112                && current.definition == possible_binding.definition
4113        }) {
4114            return;
4115        }
4116        environment.insert(
4117            name.to_string(),
4118            MacroBinding::ambiguous(conditional_file, conditional_byte),
4119        );
4120    }
4121
4122    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4123        let cell = self
4124            .macro_include_protection_cells
4125            .lock()
4126            .expect("C++ include protection cache poisoned")
4127            .entry(file.clone())
4128            .or_default()
4129            .clone();
4130        cell.get_or_init(|| {
4131            self.cpp.prepared_syntax(self.token, file).map_or(
4132                MacroIncludeProtection::None,
4133                |prepared| {
4134                    top_level_macro_include_protection(
4135                        prepared.tree().root_node(),
4136                        prepared.source(),
4137                    )
4138                },
4139            )
4140        })
4141        .clone()
4142    }
4143
4144    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4145        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4146            return Vec::new();
4147        };
4148        let source = prepared.source();
4149        let mut events = Vec::new();
4150        let root = prepared.tree().root_node();
4151        let mut stack = vec![root];
4152        while let Some(node) = stack.pop() {
4153            match node.kind() {
4154                "preproc_def" | "preproc_function_def" => {
4155                    let Some(name) = node.child_by_field_name("name") else {
4156                        continue;
4157                    };
4158                    let name = node_text(name, source).to_string();
4159                    events.push(MacroEvent::Define {
4160                        name,
4161                        binding: MacroBinding {
4162                            source: file.clone(),
4163                            declaration_byte: node.start_byte(),
4164                            definition: Self::decode_macro_definition(node, source),
4165                            exact: true,
4166                        },
4167                        byte: node.start_byte(),
4168                        conditionals: owning_preprocessor_conditionals(root, node, source),
4169                    });
4170                    continue;
4171                }
4172                "preproc_include" => {
4173                    let Some(path) = node.child_by_field_name("path") else {
4174                        events.push(MacroEvent::Include {
4175                            targets: Vec::new(),
4176                            byte: node.start_byte(),
4177                            conditionals: owning_preprocessor_conditionals(root, node, source),
4178                        });
4179                        continue;
4180                    };
4181                    let include = structured_include_path(path, source);
4182                    let targets = include.map_or_else(Vec::new, |include| {
4183                        resolve_include_targets_with_index(
4184                            file,
4185                            include,
4186                            self.cpp.include_target_index(),
4187                        )
4188                    });
4189                    // An include no indexed file can answer -- its final path component names
4190                    // no analyzable workspace file -- crosses into sources this index does not
4191                    // hold, so it cannot hide indexed macro state and must not poison every
4192                    // later local macro binding. The include's spelling does not decide that:
4193                    // a quoted include falls back to the same search path an angle include
4194                    // uses, and esphome's `core/log.h` reaches every component with
4195                    // `#include "WString.h"`, an Arduino header absent from the tree (#3057).
4196                    // A computed include, and a spelling some indexed file name does match
4197                    // without resolving to one target, may still hide indexed macro state and
4198                    // therefore fail closed.
4199                    if targets.is_empty()
4200                        && include.is_some_and(|include| {
4201                            !self.cpp.include_target_index().names_indexed_file(include)
4202                        })
4203                    {
4204                        continue;
4205                    }
4206                    events.push(MacroEvent::Include {
4207                        targets,
4208                        byte: node.start_byte(),
4209                        conditionals: owning_preprocessor_conditionals(root, node, source),
4210                    });
4211                    continue;
4212                }
4213                "preproc_call" => {
4214                    let Some(directive) = node.child_by_field_name("directive") else {
4215                        continue;
4216                    };
4217                    if node_text(directive, source) != "#undef" {
4218                        continue;
4219                    }
4220                    let name = node
4221                        .child_by_field_name("argument")
4222                        .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
4223                    if let Some(name) = name {
4224                        events.push(MacroEvent::Undef {
4225                            name,
4226                            byte: node.start_byte(),
4227                            conditionals: owning_preprocessor_conditionals(root, node, source),
4228                        });
4229                    } else {
4230                        events.push(MacroEvent::Invalidate {
4231                            byte: node.start_byte(),
4232                        });
4233                    }
4234                    continue;
4235                }
4236                _ => {}
4237            }
4238            push_named_children_reversed(node, &mut stack);
4239        }
4240        events.sort_by_key(MacroEvent::byte);
4241        events
4242    }
4243
4244    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4245        self.ordinary_type_import_cells
4246            .lock()
4247            .expect("C++ ordinary type import cache poisoned")
4248            .entry(file.clone())
4249            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4250            .clone()
4251    }
4252
4253    pub fn project_using_index(
4254        &self,
4255        build: impl FnOnce() -> ProjectUsingIndex,
4256    ) -> &ProjectUsingIndex {
4257        self.project_using_index.get_or_init(build)
4258    }
4259
4260    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4261        let mut files = self
4262            .visible_source_files_by_root
4263            .values()
4264            .flatten()
4265            .cloned()
4266            .collect::<HashSet<_>>()
4267            .into_iter()
4268            .collect::<Vec<_>>();
4269        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4270        files
4271    }
4272
4273    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4274        self.visible_source_files_by_root
4275            .get(root)
4276            .is_some_and(|files| files.contains(source))
4277    }
4278
4279    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4280        let cached = self
4281            .visible_parser_alias_name_sets
4282            .read()
4283            .expect("visible parser alias-name cache poisoned")
4284            .get(file)
4285            .cloned();
4286        let cell = if let Some(cached) = cached {
4287            cached
4288        } else {
4289            let mut cells = self
4290                .visible_parser_alias_name_sets
4291                .write()
4292                .expect("visible parser alias-name cache poisoned");
4293            Arc::clone(
4294                cells
4295                    .entry(file.clone())
4296                    .or_insert_with(|| Arc::new(OnceLock::new())),
4297            )
4298        };
4299        cell.get_or_init(|| {
4300            #[cfg(any(test, feature = "test-support"))]
4301            self.visible_parser_alias_name_set_build_count
4302                .fetch_add(1, Ordering::Relaxed);
4303            let mut names = HashSet::default();
4304            let visible_files = self
4305                .visible_source_files_by_root
4306                .get(file)
4307                .cloned()
4308                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4309            for visible_file in visible_files {
4310                let aliases = {
4311                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4312                    Arc::clone(
4313                        cells
4314                            .entry(visible_file.clone())
4315                            .or_insert_with(|| Arc::new(OnceLock::new())),
4316                    )
4317                };
4318                for alias in aliases
4319                    .get_or_init(|| {
4320                        self.parser_alias_source_parses
4321                            .fetch_add(1, Ordering::Relaxed);
4322                        #[cfg(any(test, feature = "test-support"))]
4323                        {
4324                            *self
4325                                .alias_source_parse_counts
4326                                .lock()
4327                                .expect("alias source parse count lock")
4328                                .entry(visible_file.clone())
4329                                .or_default() += 1;
4330                        }
4331                        aliases_from_prepared_source(self.cpp, self.token, &visible_file)
4332                            .into_boxed_slice()
4333                    })
4334                    .iter()
4335                {
4336                    names.insert(alias.name.clone());
4337                }
4338            }
4339            names
4340        })
4341        .contains(name)
4342    }
4343
4344    pub fn parser_alias_name_may_resolve_to_target(
4345        &self,
4346        file: &ProjectFile,
4347        alias_name: &str,
4348        target: &CodeUnit,
4349    ) -> bool {
4350        let started = std::time::Instant::now();
4351        self.parser_alias_fallback_calls
4352            .fetch_add(1, Ordering::Relaxed);
4353        let key = (
4354            file.clone(),
4355            alias_name.to_string(),
4356            logical_symbol_key(target),
4357        );
4358        let cached = self
4359            .parser_alias_target_matches
4360            .read()
4361            .expect("parser alias target-match cache poisoned")
4362            .get(&key)
4363            .cloned();
4364        let cell = if let Some(cached) = cached {
4365            cached
4366        } else {
4367            let mut cells = self
4368                .parser_alias_target_matches
4369                .write()
4370                .expect("parser alias target-match cache poisoned");
4371            Arc::clone(
4372                cells
4373                    .entry(key)
4374                    .or_insert_with(|| Arc::new(OnceLock::new())),
4375            )
4376        };
4377        let matched = *cell.get_or_init(|| match self.visible_source_files_by_root.get(file) {
4378            None => {
4379                self.parser_alias_fallback_files
4380                    .fetch_add(1, Ordering::Relaxed);
4381                self.file_alias_matches(self.cpp, file, alias_name, target)
4382            }
4383            Some(visible_files) => visible_files.iter().any(|visible_file| {
4384                self.parser_alias_fallback_files
4385                    .fetch_add(1, Ordering::Relaxed);
4386                self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4387            }),
4388        });
4389        self.parser_alias_fallback_elapsed_micros.fetch_add(
4390            started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4391            Ordering::Relaxed,
4392        );
4393        matched
4394    }
4395
4396    fn file_alias_matches(
4397        &self,
4398        cpp: &dyn CppSource,
4399        file: &ProjectFile,
4400        alias_name: &str,
4401        target: &CodeUnit,
4402    ) -> bool {
4403        let cell = {
4404            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4405            Arc::clone(
4406                cells
4407                    .entry(file.clone())
4408                    .or_insert_with(|| Arc::new(OnceLock::new())),
4409            )
4410        };
4411        cell.get_or_init(|| {
4412            self.parser_alias_source_parses
4413                .fetch_add(1, Ordering::Relaxed);
4414            #[cfg(any(test, feature = "test-support"))]
4415            {
4416                *self
4417                    .alias_source_parse_counts
4418                    .lock()
4419                    .expect("alias source parse count lock")
4420                    .entry(file.clone())
4421                    .or_default() += 1;
4422            }
4423            aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
4424        })
4425        .iter()
4426        .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
4427    }
4428
4429    fn callable_arities_for_target(
4430        &self,
4431        analyzer: &CppGraphSource<'_>,
4432        cpp: &dyn CppSource,
4433        file: &ProjectFile,
4434        prepared: &PreparedSyntaxTree,
4435        spec: &TargetSpec,
4436    ) -> Vec<ActivatedCallableArity> {
4437        let Some(signature) = spec.target.signature() else {
4438            return Vec::new();
4439        };
4440        let Some(candidates) = self
4441            .visible_by_identifier
4442            .get(file)
4443            .and_then(|by_name| by_name.get(&spec.member_name))
4444        else {
4445            return Vec::new();
4446        };
4447        let differing_candidates = candidates
4448            .iter()
4449            .filter(|candidate| {
4450                candidate.is_function()
4451                    && candidate.fq_name() == spec.target.fq_name()
4452                    && candidate.signature() == Some(signature)
4453            })
4454            .filter_map(|candidate| {
4455                analyzer
4456                    .signature_metadata(candidate)
4457                    .into_iter()
4458                    .find_map(|metadata| metadata.callable_arity())
4459                    .filter(|arity| Some(*arity) != spec.callable_arity)
4460                    .map(|arity| (candidate, arity))
4461            })
4462            .collect::<Vec<_>>();
4463        if differing_candidates.is_empty() {
4464            return Vec::new();
4465        }
4466        let mut arities = Vec::with_capacity(differing_candidates.len());
4467        // The activation ranges here describe the whole file rather than one
4468        // reference, so there is no reference guard environment to consult.
4469        let reference = CallableReferenceContext {
4470            file,
4471            position: None,
4472        };
4473        for (candidate, candidate_arity) in differing_candidates {
4474            let declaration_activation = if candidate.source() == file {
4475                callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
4476            } else {
4477                cpp.prepared_syntax(self.token, candidate.source())
4478                    .and_then(|syntax| {
4479                        callable_declaration_activation_in_file(
4480                            analyzer,
4481                            syntax.as_ref(),
4482                            candidate,
4483                            &reference,
4484                        )
4485                    })
4486            };
4487            let Some(declaration_activation) = declaration_activation else {
4488                continue;
4489            };
4490            let activation_byte = if candidate.source() == file {
4491                Some(declaration_activation)
4492            } else {
4493                self.include_activation_for_source(cpp, file, prepared, candidate.source())
4494            };
4495            if let Some(activation_byte) = activation_byte {
4496                arities.push(ActivatedCallableArity {
4497                    activation_byte,
4498                    arity: candidate_arity,
4499                });
4500            }
4501        }
4502        arities
4503    }
4504
4505    fn callable_parameter_macro_arity(
4506        &self,
4507        target: &CodeUnit,
4508        signature: Option<&str>,
4509    ) -> Option<CallableArity> {
4510        let parameter_types = cpp_signature_param_types(signature?)?;
4511        let [macro_name] = parameter_types.as_slice() else {
4512            return None;
4513        };
4514        if macro_name.is_empty()
4515            || !macro_name
4516                .chars()
4517                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4518        {
4519            return None;
4520        }
4521        let cache_key = (target.source().clone(), macro_name.clone());
4522        if let Some(cached) = self
4523            .callable_parameter_macro_arities
4524            .lock()
4525            .expect("C++ callable parameter-macro arity cache poisoned")
4526            .get(&cache_key)
4527            .copied()
4528        {
4529            return cached;
4530        }
4531        let mut visible_files = HashSet::default();
4532        collect_include_closure(
4533            &self.cpp_source(),
4534            self.cpp.include_target_index(),
4535            target.source(),
4536            &mut visible_files,
4537            None,
4538        );
4539        let mut arities = Vec::new();
4540        for visible_file in visible_files {
4541            let cell = self.macro_event_cell(&visible_file);
4542            for event in
4543                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4544            {
4545                let MacroEvent::Define { name, binding, .. } = event else {
4546                    continue;
4547                };
4548                if name != macro_name {
4549                    continue;
4550                }
4551                let MacroDefinition::Object { replacement } = &binding.definition else {
4552                    continue;
4553                };
4554                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4555                    continue;
4556                };
4557                if !arities.contains(&arity) {
4558                    arities.push(arity);
4559                }
4560            }
4561        }
4562        let resolved = (|| {
4563            let required = arities
4564                .iter()
4565                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4566                .min()?;
4567            let total = arities.iter().map(|arity| arity.total()).max()?;
4568            let repeated = arities
4569                .iter()
4570                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4571            // Preprocessor conditions can leave more than one object-like parameter
4572            // bundle active in the target header's include closure. Preserve their
4573            // conservative callable envelope instead of choosing whichever definition
4574            // happened to be visited first.
4575            Some(CallableArity::new(required, total, repeated))
4576        })();
4577        self.callable_parameter_macro_arities
4578            .lock()
4579            .expect("C++ callable parameter-macro arity cache poisoned")
4580            .insert(cache_key, resolved);
4581        resolved
4582    }
4583
4584    pub fn include_activation_for_source(
4585        &self,
4586        cpp: &dyn CppSource,
4587        file: &ProjectFile,
4588        prepared: &PreparedSyntaxTree,
4589        donor_source: &ProjectFile,
4590    ) -> Option<usize> {
4591        let key = (file.clone(), donor_source.clone());
4592        if let Some(cached) = self
4593            .include_activation_cells
4594            .lock()
4595            .expect("C++ include activation cache poisoned")
4596            .get(&key)
4597            .copied()
4598        {
4599            return cached;
4600        }
4601        #[cfg(any(test, feature = "test-support"))]
4602        self.include_activation_build_count
4603            .fetch_add(1, Ordering::Relaxed);
4604        let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
4605        let mut cells = self
4606            .include_activation_cells
4607            .lock()
4608            .expect("C++ include activation cache poisoned");
4609        *cells.entry(key).or_insert(activation)
4610    }
4611
4612    pub fn conditional_include_projections_for_source(
4613        &self,
4614        file: &ProjectFile,
4615        prepared: &PreparedSyntaxTree,
4616        donor_source: &ProjectFile,
4617    ) -> Arc<[ConditionalIncludeProjection]> {
4618        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4619        let cell = self
4620            .conditional_include_projection_cells
4621            .lock()
4622            .expect("C++ conditional include projection cache poisoned")
4623            .entry(file.clone())
4624            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4625            .clone();
4626        let index = cell.get_or_build_pool_independent(|| {
4627            #[cfg(any(test, feature = "test-support"))]
4628            self.conditional_include_projection_index_build_count
4629                .fetch_add(1, Ordering::Relaxed);
4630            find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
4631                #[cfg(any(test, feature = "test-support"))]
4632                self.conditional_include_projection_state_count
4633                    .fetch_add(1, Ordering::Relaxed);
4634            })
4635        });
4636        index
4637            .get(donor_source)
4638            .cloned()
4639            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4640    }
4641
4642    #[cfg(any(test, feature = "test-support"))]
4643    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4644        (
4645            self.conditional_include_projection_index_build_count
4646                .load(Ordering::Relaxed),
4647            self.conditional_include_projection_state_count
4648                .load(Ordering::Relaxed),
4649        )
4650    }
4651
4652    #[cfg(any(test, feature = "test-support"))]
4653    pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4654        self.conditional_include_target_state_count
4655            .load(Ordering::Relaxed)
4656    }
4657
4658    #[cfg(any(test, feature = "test-support"))]
4659    pub fn include_activation_build_count_for_test(&self) -> usize {
4660        self.include_activation_build_count.load(Ordering::Relaxed)
4661    }
4662
4663    #[cfg(any(test, feature = "test-support"))]
4664    pub fn note_using_donor_activation_for_test(&self) {
4665        self.using_donor_activation_count
4666            .fetch_add(1, Ordering::Relaxed);
4667    }
4668
4669    #[cfg(not(any(test, feature = "test-support")))]
4670    pub fn note_using_donor_activation_for_test(&self) {}
4671
4672    #[cfg(any(test, feature = "test-support"))]
4673    pub fn note_using_namespace_lookup_for_test(&self) {
4674        self.using_namespace_lookup_count
4675            .fetch_add(1, Ordering::Relaxed);
4676    }
4677
4678    #[cfg(not(any(test, feature = "test-support")))]
4679    pub fn note_using_namespace_lookup_for_test(&self) {}
4680
4681    #[cfg(any(test, feature = "test-support"))]
4682    pub fn note_using_name_candidate_inspection_for_test(&self) {
4683        self.using_name_candidate_inspection_count
4684            .fetch_add(1, Ordering::Relaxed);
4685    }
4686
4687    #[cfg(not(any(test, feature = "test-support")))]
4688    pub fn note_using_name_candidate_inspection_for_test(&self) {}
4689
4690    #[cfg(any(test, feature = "test-support"))]
4691    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4692        (
4693            self.using_donor_activation_count.load(Ordering::Relaxed),
4694            self.using_namespace_lookup_count.load(Ordering::Relaxed),
4695            self.callable_reference_spec_build_count
4696                .load(Ordering::Relaxed),
4697            self.using_name_candidate_inspection_count
4698                .load(Ordering::Relaxed),
4699        )
4700    }
4701
4702    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4703        file == target.source()
4704            || self
4705                .visible_by_file
4706                .get(file)
4707                .is_some_and(|visible| visible.contains(target))
4708    }
4709
4710    /// Whether some declaration of `declaration`'s logical symbol is visible at
4711    /// `reference_byte` in `file`.
4712    ///
4713    /// The question is asked of the *logical* symbol, not of the physical unit:
4714    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
4715    /// and it does not have to be - what makes the call legal is the header
4716    /// declaration that the reference file does include. Reading that relation
4717    /// through `same_logical_callable` rather than through signature strings is
4718    /// the same #2010 correction the gates make, and it matters here because
4719    /// the body and the declaration are exactly the pair that spells one
4720    /// parameter type two ways.
4721    pub fn declaration_visible_at(
4722        &self,
4723        analyzer: &CppGraphSource<'_>,
4724        file: &ProjectFile,
4725        declaration: &CodeUnit,
4726        reference_byte: usize,
4727    ) -> bool {
4728        let reference_guards = OnceCell::new();
4729        self.visible_identifier_candidates(file, declaration.identifier())
4730            .filter(|candidate| {
4731                self.same_logical_callable(analyzer, candidate, declaration)
4732                    || flattened_macro_namespace_declaration_matches(
4733                        analyzer,
4734                        self.cpp,
4735                        file,
4736                        candidate,
4737                        declaration,
4738                        reference_byte,
4739                    )
4740            })
4741            .any(|candidate| {
4742                self.physical_declaration_visible_at(
4743                    analyzer,
4744                    file,
4745                    candidate,
4746                    reference_byte,
4747                    &reference_guards,
4748                )
4749            })
4750    }
4751
4752    /// Whether a physical declaration is visible at an exact structured
4753    /// reference node. Inverse C field references need the reference's own
4754    /// preprocessor environment before using the callable activation path:
4755    /// callable activation can establish that a field declaration is
4756    /// nameable, but it must not admit the opposite branch of that field's
4757    /// conditional family.
4758    pub fn declaration_visible_at_reference(
4759        &self,
4760        analyzer: &CppGraphSource<'_>,
4761        file: &ProjectFile,
4762        declaration: &CodeUnit,
4763        reference: Node<'_>,
4764    ) -> bool {
4765        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4766            return false;
4767        };
4768        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4769        let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4770        if !declaration_guards.iter().any(|(_, required)| {
4771            guards_compatible_at_reference(required, reference_guards.as_ref())
4772        }) {
4773            return false;
4774        }
4775        if declaration.source() == file
4776            && !analyzer.reference_uses_c_semantics(file)
4777            && (declaration.is_field() || declaration.is_callable())
4778            && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4779                self.indexed_enclosing_owner_scope(analyzer, file, reference)
4780                    .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4781            })
4782        {
4783            // C++ complete-class context makes members visible throughout the
4784            // class. The guard check above still excludes a declaration from
4785            // an incompatible preprocessor branch.
4786            return true;
4787        }
4788        if declaration.is_field() && declaration.source() == file {
4789            let reference_byte = reference.start_byte();
4790            let reference_function =
4791                real_function_definition_ancestor(reference, prepared.source());
4792            let guards = OnceCell::new();
4793            let field_reference = CallableReferenceContext {
4794                file,
4795                position: Some(CallableReferencePosition {
4796                    prepared: prepared.as_ref(),
4797                    byte: reference_byte,
4798                    guards: &guards,
4799                }),
4800            };
4801            let mut has_local_declaration = false;
4802            let mut local_declaration_visible = false;
4803            for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4804            {
4805                let Some(declaration_function) =
4806                    real_function_definition_ancestor(declaration, prepared.source())
4807                else {
4808                    continue;
4809                };
4810                has_local_declaration = true;
4811                if reference_function.is_some_and(|reference_function| {
4812                    reference_function.start_byte() == declaration_function.start_byte()
4813                        && reference_function.end_byte() == declaration_function.end_byte()
4814                }) && callable_preprocessor_context_is_visible_for_reference(
4815                    declaration,
4816                    prepared.source(),
4817                    &field_reference,
4818                ) && callable_declaration_activation_byte(declaration) < reference_byte
4819                {
4820                    local_declaration_visible = true;
4821                    break;
4822                }
4823            }
4824            if has_local_declaration {
4825                return local_declaration_visible;
4826            }
4827        }
4828        let guards = OnceCell::new();
4829        self.physical_declaration_visible_at(
4830            analyzer,
4831            file,
4832            declaration,
4833            reference.start_byte(),
4834            &guards,
4835        )
4836    }
4837
4838    /// C forward navigation may bind a call to a later same-file definition.
4839    /// There is no earlier source declaration to activate in that legacy C
4840    /// shape, but the call's preprocessor environment must still imply the
4841    /// definition's requirements. Ordinary C++ and inverse visibility retain
4842    /// the declaration-order rule in [`Self::declaration_visible_at`].
4843    pub fn declaration_visible_for_c_forward_call(
4844        &self,
4845        analyzer: &CppGraphSource<'_>,
4846        file: &ProjectFile,
4847        declaration: &CodeUnit,
4848        reference_byte: usize,
4849    ) -> bool {
4850        if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4851            return true;
4852        }
4853        if declaration.source() != file {
4854            return false;
4855        }
4856        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4857            return false;
4858        };
4859        let reference_guards = prepared
4860            .tree()
4861            .root_node()
4862            .descendant_for_byte_range(reference_byte, reference_byte)
4863            .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4864        declaration_guard_requirements(analyzer, self.cpp, declaration)
4865            .into_iter()
4866            .any(|(_, required)| {
4867                guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4868            })
4869    }
4870
4871    pub fn callable_arity_at_reference(
4872        &self,
4873        analyzer: &CppGraphSource<'_>,
4874        file: &ProjectFile,
4875        candidate: &CodeUnit,
4876        reference_byte: usize,
4877    ) -> Option<CallableArity> {
4878        let key = (file.clone(), logical_symbol_key(candidate));
4879        let cell = self
4880            .callable_reference_specs
4881            .lock()
4882            .expect("C++ callable reference-spec cache poisoned")
4883            .entry(key)
4884            .or_default()
4885            .clone();
4886        let spec = cell.get_or_init(|| {
4887            let prepared = self.cpp.prepared_syntax(self.token, file)?;
4888            let spec = TargetSpec::from_target(analyzer, candidate)?;
4889            let spec = spec
4890                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4891                .into_owned();
4892            #[cfg(any(test, feature = "test-support"))]
4893            self.callable_reference_spec_build_count
4894                .fetch_add(1, Ordering::Relaxed);
4895            Some(spec)
4896        });
4897        spec.as_ref()?.callable_arity_at(reference_byte)
4898    }
4899
4900    fn physical_declaration_visible_at(
4901        &self,
4902        analyzer: &CppGraphSource<'_>,
4903        file: &ProjectFile,
4904        declaration: &CodeUnit,
4905        reference_byte: usize,
4906        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4907    ) -> bool {
4908        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4909            return false;
4910        };
4911        let reference = CallableReferenceContext {
4912            file,
4913            position: Some(CallableReferencePosition {
4914                prepared: prepared.as_ref(),
4915                byte: reference_byte,
4916                guards: reference_guards,
4917            }),
4918        };
4919        if declaration.source() == file {
4920            return callable_declaration_activation_in_file(
4921                analyzer,
4922                prepared.as_ref(),
4923                declaration,
4924                &reference,
4925            )
4926            .or_else(|| {
4927                self.exhaustive_guard_family_activation(
4928                    analyzer,
4929                    prepared.as_ref(),
4930                    declaration,
4931                    &reference,
4932                )
4933            })
4934            .is_some_and(|activation| activation < reference_byte);
4935        }
4936        let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
4937            return false;
4938        };
4939        if self
4940            .foreign_callable_declaration_activation(
4941                analyzer,
4942                donor_syntax.as_ref(),
4943                declaration,
4944                &reference,
4945            )
4946            .or_else(|| {
4947                self.exhaustive_guard_family_activation(
4948                    analyzer,
4949                    donor_syntax.as_ref(),
4950                    declaration,
4951                    &reference,
4952                )
4953            })
4954            .is_none()
4955        {
4956            return false;
4957        }
4958        declaration_guard_requirements(analyzer, self.cpp, declaration)
4959            .into_iter()
4960            .any(|(_, declaration_guards)| {
4961                self.foreign_declaration_reachable_at_reference(
4962                    file,
4963                    prepared.as_ref(),
4964                    declaration.source(),
4965                    &declaration_guards,
4966                    reference.guards(),
4967                    reference_byte,
4968                )
4969            })
4970    }
4971
4972    /// The byte at which `declaration` activates inside the foreign file that
4973    /// `donor_syntax` describes.
4974    ///
4975    /// Which guard rule applies depends on what decides the reference's
4976    /// configuration. When `compile_commands.json` covers the reference's
4977    /// translation unit, the build is the proof source and the declaration's
4978    /// guards must follow from the reference's active guards plus the proven
4979    /// defines; a platform macro the build does not prove leaves the
4980    /// declaration invisible (#2011). With no compile coverage nothing decides
4981    /// a platform macro, so the cross-file rule stands: the donor resolves its
4982    /// own conditionals and only has to stay free of contradiction with the
4983    /// reference. Otherwise a libuv-shaped `#if defined(__linux__)` prototype
4984    /// in a shared header is hidden from every unguarded caller in another
4985    /// translation unit (#2988).
4986    fn foreign_callable_declaration_activation(
4987        &self,
4988        analyzer: &CppGraphSource<'_>,
4989        donor_syntax: &PreparedSyntaxTree,
4990        declaration: &CodeUnit,
4991        reference: &CallableReferenceContext<'_>,
4992    ) -> Option<usize> {
4993        let build_decides = !self.compile_context_is_absent(reference.file);
4994        let proven = self.compile_proven_guards(reference.file);
4995        let augmented;
4996        let active = match reference.guards() {
4997            Some(active) if !proven.is_empty() => {
4998                augmented = active.union(&proven).cloned().collect();
4999                Some(&augmented)
5000            }
5001            other => other,
5002        };
5003        nameable_callable_declaration_nodes(analyzer, donor_syntax, declaration)
5004            .into_iter()
5005            .filter(|node| {
5006                let Some(required) = callable_declaration_guard_requirements(
5007                    *node,
5008                    donor_syntax.source(),
5009                    reference,
5010                ) else {
5011                    return false;
5012                };
5013                if required.is_empty() {
5014                    return true;
5015                }
5016                if build_decides {
5017                    guard_requirements_hold_at_reference(&required, active)
5018                } else {
5019                    guards_compatible_at_reference(&required, reference.guards())
5020                }
5021            })
5022            .map(callable_declaration_activation_byte)
5023            .min()
5024    }
5025
5026    pub fn external_type_candidate_visible_at(
5027        &self,
5028        file: &ProjectFile,
5029        candidate: &CodeUnit,
5030        reference_byte: usize,
5031    ) -> bool {
5032        if candidate.source() == file {
5033            return true;
5034        }
5035        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5036            return false;
5037        };
5038        self.visible_identifier_candidates(file, candidate.identifier())
5039            .filter(|peer| same_logical_symbol(candidate, peer))
5040            .any(|peer| {
5041                peer.source() == file
5042                    || self
5043                        .include_activation_for_source(
5044                            self.cpp,
5045                            file,
5046                            prepared.as_ref(),
5047                            peer.source(),
5048                        )
5049                        .is_some_and(|activation| activation <= reference_byte)
5050            })
5051    }
5052
5053    pub fn external_type_declaration_visible_at(
5054        &self,
5055        file: &ProjectFile,
5056        candidate: &CodeUnit,
5057        reference_byte: usize,
5058    ) -> bool {
5059        if candidate.source() == file {
5060            return true;
5061        }
5062        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5063            return false;
5064        };
5065        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5066            .is_some_and(|activation| activation <= reference_byte)
5067    }
5068
5069    /// The preprocessor facts the build proves for a reference sited in
5070    /// `file` (#2011).
5071    ///
5072    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
5073    /// `Defined` fact, and a fact holds only when every compile configuration
5074    /// that governs the file agrees on it (intersection). The facts are
5075    /// strictly additive to the reference's active guard set: they can prove a
5076    /// required guard, but the guard check itself is never weakened and no
5077    /// implication is ever inferred from source text.
5078    ///
5079    /// A file with its own database entry answers from that entry alone
5080    /// (phase 1). A header takes its context from the translation units whose
5081    /// include closure reaches it, intersected across all of them (phase 2):
5082    /// the header is compiled once per including TU, so a fact holds for a
5083    /// header-sited reference only when every one of those compilations
5084    /// proves it. A reaching TU the database does not cover proves nothing,
5085    /// which empties the intersection. A file nothing covers or reaches has
5086    /// no facts and every check runs on source structure alone.
5087    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5088        if let Some(cached) = self
5089            .compile_proven_guard_cells
5090            .lock()
5091            .expect("C++ compile-proven guard cache poisoned")
5092            .get(file)
5093        {
5094            return Arc::clone(cached);
5095        }
5096        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5097            Some(names) => names,
5098            None => {
5099                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5100                let seed = translation_units.next().and_then(|translation_unit| {
5101                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5102                });
5103                match seed {
5104                    None => HashSet::default(),
5105                    Some(mut names) => {
5106                        for translation_unit in translation_units {
5107                            let Some(reached) = context_fact_names(
5108                                self.cpp.compile_contexts_for(&translation_unit),
5109                            ) else {
5110                                names.clear();
5111                                break;
5112                            };
5113                            names.retain(|name| reached.contains(name));
5114                            if names.is_empty() {
5115                                break;
5116                            }
5117                        }
5118                        names
5119                    }
5120                }
5121            }
5122        };
5123        let proven = Arc::new(
5124            names
5125                .into_iter()
5126                .map(PreprocessorGuard::Defined)
5127                .collect::<HashSet<_>>(),
5128        );
5129        self.compile_proven_guard_cells
5130            .lock()
5131            .expect("C++ compile-proven guard cache poisoned")
5132            .insert(file.clone(), Arc::clone(&proven));
5133        proven
5134    }
5135
5136    /// The rule a conditional `#include` path's guards face on the way to a
5137    /// reference in `file`.
5138    ///
5139    /// This is the include-edge analogue of the choice
5140    /// [`Self::foreign_callable_declaration_activation`] makes for a foreign
5141    /// declaration's own guards (#2988), and it is decided by the same thing:
5142    /// what settles the reference's configuration. Memoized per file because
5143    /// the answer depends on nothing else, and the callers ask it once per
5144    /// candidate.
5145    fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5146        if let Some(cached) = self
5147            .include_path_admission_cells
5148            .lock()
5149            .expect("C++ include-path admission cache poisoned")
5150            .get(file)
5151            .copied()
5152        {
5153            return cached;
5154        }
5155        let admission = if self.compile_context_is_absent(file) {
5156            IncludePathAdmission::Compatible
5157        } else {
5158            IncludePathAdmission::Proven
5159        };
5160        self.include_path_admission_cells
5161            .lock()
5162            .expect("C++ include-path admission cache poisoned")
5163            .insert(file.clone(), admission);
5164        admission
5165    }
5166
5167    /// Whether no compile data covers the compilations of `file`: it has no
5168    /// database entry of its own, and either nothing reaches it or some
5169    /// translation unit that reaches it has no entry. This is the state a
5170    /// regenerated `compile_commands.json` could decide; data that is present
5171    /// for every governing compilation but does not prove a guard is a
5172    /// decided conservative miss, not this state.
5173    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5174        if !self.cpp.compile_contexts_for(file).is_empty() {
5175            return false;
5176        }
5177        let translation_units = self.cpp.reaching_translation_units(file);
5178        translation_units.is_empty()
5179            || translation_units
5180                .iter()
5181                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5182    }
5183
5184    /// Whether a lookup miss for `identifier` in `file` is explainable by
5185    /// missing compile context (#2011): some same-name declaration is
5186    /// reachable through a conditional include whose required guards neither
5187    /// contradict the reference's active guards nor follow from them, and the
5188    /// translation unit has no compile-commands entry that could decide the
5189    /// question. Callers surface this as an explicit "requires compile
5190    /// context" incompleteness instead of an indistinguishable miss.
5191    ///
5192    /// A structurally disproven declaration (contradicting guards) and a TU
5193    /// whose compile context exists but does not prove the guard both answer
5194    /// `false`: those misses are decided, not incomplete.
5195    pub fn miss_requires_compile_context(
5196        &self,
5197        file: &ProjectFile,
5198        identifier: &str,
5199        reference: Node<'_>,
5200    ) -> bool {
5201        if !self.compile_context_is_absent(file) {
5202            return false;
5203        }
5204        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5205            return false;
5206        };
5207        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5208        let reference_byte = reference.start_byte();
5209        let mut sources = self
5210            .visible_identifier_candidates(file, identifier)
5211            .map(CodeUnit::source)
5212            .filter(|source| *source != file)
5213            .collect::<Vec<_>>();
5214        sources.sort();
5215        sources.dedup();
5216        sources.into_iter().any(|declaration_source| {
5217            self.conditional_include_projections_for_source(
5218                file,
5219                prepared.as_ref(),
5220                declaration_source,
5221            )
5222            .iter()
5223            .any(|projection| {
5224                projection.activation_byte <= reference_byte
5225                    && !guard_requirements_hold_at_reference(
5226                        &projection.required_guards,
5227                        reference_guards.as_ref(),
5228                    )
5229                    && guards_compatible_at_reference(
5230                        &projection.required_guards,
5231                        reference_guards.as_ref(),
5232                    )
5233            })
5234        })
5235    }
5236
5237    /// Decide whether a declaration that lives in another file reaches a
5238    /// reference in `file`.
5239    ///
5240    /// An external header selects its declaration branch before the reference
5241    /// file is parsed. Require compatible reference guards, but do not test
5242    /// the header's guard expression for stability in the reference file: a
5243    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
5244    /// wraps every declaration of a portable C header, and demanding it would
5245    /// hide the whole header. Guards that the reference file imposes on its
5246    /// own `#include` still have to be stable, and must satisfy the rule
5247    /// [`IncludePathAdmission`] names for this reference's translation unit.
5248    fn foreign_declaration_reachable_at_reference(
5249        &self,
5250        file: &ProjectFile,
5251        prepared: &PreparedSyntaxTree,
5252        declaration_source: &ProjectFile,
5253        declaration_guards: &HashSet<PreprocessorGuard>,
5254        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5255        reference_byte: usize,
5256    ) -> bool {
5257        // The translation unit's build-proven defines join the reference's
5258        // active guard set (#2011): a conditional include like the nng
5259        // `NNG_PLATFORM_POSIX` chain is provable only by the compile command.
5260        // A reference whose own environment is unknown stays unknown -- the
5261        // facts extend an environment, they never invent one.
5262        let proven = self.compile_proven_guards(file);
5263        let augmented;
5264        let reference_guards = match reference_guards {
5265            Some(active) if !proven.is_empty() => {
5266                augmented = active.union(&proven).cloned().collect();
5267                Some(&augmented)
5268            }
5269            other => other,
5270        };
5271        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5272            eprintln!(
5273                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5274                declaration_source.rel_path().display(),
5275            );
5276        }
5277        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5278            return false;
5279        }
5280        if self
5281            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5282            .is_some_and(|activation| activation <= reference_byte)
5283        {
5284            return true;
5285        }
5286        let projections =
5287            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5288        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5289            eprintln!(
5290                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5291                declaration_source.rel_path().display(),
5292                declaration_guards.len(),
5293                proven.len(),
5294                projections.len(),
5295            );
5296        }
5297        let admission = self.include_path_admission(file);
5298        projections.iter().any(|projection| {
5299            projection.activation_byte <= reference_byte
5300                && admission.admits(
5301                    &projection.required_guards,
5302                    &projection.partial_guards,
5303                    reference_guards,
5304                )
5305                && self.preprocessor_guards_stable_between(
5306                    file,
5307                    projection.activation_byte,
5308                    reference_byte,
5309                    &projection.required_guards,
5310                )
5311        })
5312    }
5313
5314    fn foreign_declaration_may_be_reachable_from_raw_guards(
5315        &self,
5316        file: &ProjectFile,
5317        prepared: &PreparedSyntaxTree,
5318        declaration_source: &ProjectFile,
5319        declaration_guards: &HashSet<PreprocessorGuard>,
5320        reference_guards: Option<&HashSet<PreprocessorGuard>>,
5321        reference_byte: usize,
5322    ) -> bool {
5323        let proven = self.compile_proven_guards(file);
5324        let augmented;
5325        let reference_guards = match reference_guards {
5326            Some(active) if !proven.is_empty() => {
5327                augmented = active.union(&proven).cloned().collect();
5328                Some(&augmented)
5329            }
5330            other => other,
5331        };
5332        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5333            return false;
5334        }
5335        if self
5336            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5337            .is_some_and(|activation| activation <= reference_byte)
5338        {
5339            return true;
5340        }
5341        let reachable = find_conditional_include_projection_for_source(
5342            self.cpp,
5343            self.token,
5344            file,
5345            prepared,
5346            declaration_source,
5347            self.include_path_admission(file),
5348            reference_guards,
5349            reference_byte,
5350            &|| {
5351                #[cfg(any(test, feature = "test-support"))]
5352                self.conditional_include_target_state_count
5353                    .fetch_add(1, Ordering::Relaxed);
5354            },
5355        );
5356        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5357            eprintln!(
5358                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5359                declaration_source.rel_path().display(),
5360                declaration_guards.len(),
5361                proven.len(),
5362                reference_guards.map_or(0, HashSet::len),
5363            );
5364        }
5365        reachable
5366    }
5367
5368    fn foreign_declaration_reachable_from_compile_proven_guards(
5369        &self,
5370        file: &ProjectFile,
5371        prepared: &PreparedSyntaxTree,
5372        declaration_source: &ProjectFile,
5373        declaration_guards: &HashSet<PreprocessorGuard>,
5374        reference_byte: usize,
5375    ) -> bool {
5376        let proven = self.compile_proven_guards(file);
5377        if proven.is_empty()
5378            || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5379        {
5380            return false;
5381        }
5382        let projections =
5383            self.conditional_include_projections_for_source(file, prepared, declaration_source);
5384        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5385            eprintln!(
5386                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5387                declaration_source.rel_path().display(),
5388                declaration_guards.len(),
5389                proven.len(),
5390                projections.len(),
5391            );
5392        }
5393        projections.iter().any(|projection| {
5394            projection.activation_byte <= reference_byte
5395                    && guard_requirements_hold_at_reference(
5396                        &projection.required_guards,
5397                        Some(proven.as_ref()),
5398                    )
5399                    // Build facts hold at translation-unit entry. A source
5400                    // `#undef` or an earlier include may invalidate one before
5401                    // this conditional include is reached; mutations after the
5402                    // include cannot revoke declarations it already supplied.
5403                    && self.preprocessor_guards_stable_between(
5404                        file,
5405                        0,
5406                        projection.activation_byte,
5407                        &projection.required_guards,
5408                    )
5409        })
5410    }
5411
5412    pub fn external_type_candidate_visible_in_context(
5413        &self,
5414        analyzer: &CppGraphSource<'_>,
5415        file: &ProjectFile,
5416        candidate: &CodeUnit,
5417        reference: Node<'_>,
5418    ) -> bool {
5419        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5420        if report_stats {
5421            eprintln!(
5422                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5423                candidate.fq_name(),
5424                candidate.source().rel_path().display(),
5425                file.rel_path().display(),
5426                reference.start_byte(),
5427            );
5428        }
5429        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5430            return false;
5431        };
5432        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5433        let reference_guards = OnceCell::new();
5434        let reference_guards_at_site = || {
5435            reference_guards.get_or_init(|| {
5436                let started = Instant::now();
5437                if report_stats {
5438                    eprintln!(
5439                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5440                        file.rel_path().display(),
5441                        reference.start_byte(),
5442                        raw_reference_guards.as_ref().map_or(0, HashSet::len),
5443                    );
5444                }
5445                let macro_environment = self.macro_environment(file, reference.start_byte());
5446                let filtered = raw_reference_guards
5447                    .clone()
5448                    .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5449                if report_stats {
5450                    eprintln!(
5451                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5452                        filtered.is_some(),
5453                        started.elapsed().as_millis(),
5454                    );
5455                }
5456                filtered
5457            })
5458        };
5459
5460        let peers = self
5461            .visible_identifier_candidates(file, candidate.identifier())
5462            .filter(|peer| same_logical_symbol(candidate, peer))
5463            .collect::<Vec<_>>();
5464        if report_stats {
5465            let peer_sources = peers
5466                .iter()
5467                .map(|peer| peer.source().rel_path().display().to_string())
5468                .collect::<Vec<_>>();
5469            eprintln!(
5470                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5471                candidate.fq_name(),
5472            );
5473        }
5474        let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5475            declaration_guard_requirements(analyzer, self.cpp, peer)
5476                .into_iter()
5477                .any(|(declaration_byte, declaration_guards)| {
5478                    if peer.source() == file {
5479                        let visible = declaration_byte < reference.start_byte()
5480                            && declaration_guards.is_empty();
5481                        if report_stats {
5482                            eprintln!(
5483                                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5484                                peer.source().rel_path().display(),
5485                                declaration_guards.len(),
5486                            );
5487                        }
5488                        return visible;
5489                    }
5490                    let direct = declaration_guards.is_empty()
5491                        && self
5492                            .include_activation_for_source(
5493                                self.cpp,
5494                                file,
5495                                prepared.as_ref(),
5496                                peer.source(),
5497                            )
5498                            .is_some_and(|activation| activation <= reference.start_byte());
5499                    let compile_proven = !direct
5500                        && self.foreign_declaration_reachable_from_compile_proven_guards(
5501                            file,
5502                            prepared.as_ref(),
5503                            peer.source(),
5504                            &declaration_guards,
5505                            reference.start_byte(),
5506                        );
5507                    if report_stats {
5508                        eprintln!(
5509                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5510                            peer.source().rel_path().display(),
5511                            declaration_guards.len(),
5512                        );
5513                    }
5514                    direct || compile_proven
5515                })
5516        });
5517        if directly_visible_without_reference_environment {
5518            if report_stats {
5519                eprintln!(
5520                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5521                    candidate.fq_name(),
5522                );
5523            }
5524            return true;
5525        }
5526        let directly_visible = peers.iter().any(|peer| {
5527            declaration_guard_requirements(analyzer, self.cpp, peer)
5528                .into_iter()
5529                .any(|(declaration_byte, declaration_guards)| {
5530                    if peer.source() == file {
5531                        if declaration_byte >= reference.start_byte() {
5532                            return false;
5533                        }
5534                        if !guard_requirements_hold_at_reference(
5535                            &declaration_guards,
5536                            raw_reference_guards.as_ref(),
5537                        ) {
5538                            return false;
5539                        }
5540                        return guard_requirements_hold_at_reference(
5541                            &declaration_guards,
5542                            reference_guards_at_site().as_ref(),
5543                        ) && self.preprocessor_guards_stable_between(
5544                            file,
5545                            declaration_byte,
5546                            reference.start_byte(),
5547                            &declaration_guards,
5548                        );
5549                    }
5550                    let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5551                        file,
5552                        prepared.as_ref(),
5553                        peer.source(),
5554                        &declaration_guards,
5555                        raw_reference_guards.as_ref(),
5556                        reference.start_byte(),
5557                    );
5558                    if report_stats {
5559                        eprintln!(
5560                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5561                            peer.source().rel_path().display(),
5562                            declaration_guards.len(),
5563                        );
5564                    }
5565                    if !raw_feasible {
5566                        return false;
5567                    }
5568                    self.foreign_declaration_reachable_at_reference(
5569                        file,
5570                        prepared.as_ref(),
5571                        peer.source(),
5572                        &declaration_guards,
5573                        reference_guards_at_site().as_ref(),
5574                        reference.start_byte(),
5575                    )
5576                })
5577        });
5578        if directly_visible {
5579            if report_stats {
5580                eprintln!(
5581                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5582                    candidate.fq_name(),
5583                );
5584            }
5585            return true;
5586        }
5587        let complementary = self
5588            .visible_identifier_candidates(file, candidate.identifier())
5589            .filter(|peer| {
5590                peer.kind() == candidate.kind()
5591                    && peer.fq_name() == candidate.fq_name()
5592                    && peer.source() == candidate.source()
5593            })
5594            .collect::<Vec<_>>();
5595        // A completed #if/#else family declares the shared source-level name
5596        // before this reference. A later macro mutation cannot revoke that
5597        // declaration. The family gate below rejects declarations split across
5598        // separate conditional blocks, where mutation can change coverage.
5599        let complementary_family =
5600            self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5601        let raw_candidate_branch_compatible = complementary_family
5602            && raw_reference_guards.as_ref().is_some_and(|active| {
5603                declaration_guard_requirements(analyzer, self.cpp, candidate)
5604                    .iter()
5605                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5606            });
5607        if report_stats {
5608            eprintln!(
5609                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5610                candidate.fq_name(),
5611                complementary.len(),
5612                complementary_family,
5613                raw_candidate_branch_compatible,
5614            );
5615        }
5616        let candidate_branch_compatible = raw_candidate_branch_compatible
5617            && reference_guards_at_site().as_ref().is_some_and(|active| {
5618                declaration_guard_requirements(analyzer, self.cpp, candidate)
5619                    .iter()
5620                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5621            });
5622        let complementary_visible = candidate_branch_compatible
5623            && if candidate.source() == file {
5624                declaration_guard_requirements(analyzer, self.cpp, candidate)
5625                    .iter()
5626                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5627            } else {
5628                self.include_activation_for_source(
5629                    self.cpp,
5630                    file,
5631                    prepared.as_ref(),
5632                    candidate.source(),
5633                )
5634                .is_some_and(|activation| activation <= reference.start_byte())
5635            };
5636        if report_stats {
5637            eprintln!(
5638                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5639                if complementary_visible {
5640                    "complementary"
5641                } else {
5642                    "missing"
5643                },
5644                candidate.fq_name(),
5645            );
5646        }
5647        complementary_visible
5648    }
5649
5650    pub fn is_exhaustive_same_fqn_type_declaration_family(
5651        &self,
5652        analyzer: &CppGraphSource<'_>,
5653        file: &ProjectFile,
5654        candidate: &CodeUnit,
5655    ) -> bool {
5656        let candidates = self
5657            .visible_identifier_candidates(file, candidate.identifier())
5658            .filter(|peer| {
5659                peer.kind() == candidate.kind()
5660                    && peer.fq_name() == candidate.fq_name()
5661                    && peer.source() == candidate.source()
5662            })
5663            .collect::<Vec<_>>();
5664        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5665    }
5666
5667    /// Prove a nested type alias used as a dependent member-pointer owner when
5668    /// its owning class has mutually-exclusive declarations.  A common C++11
5669    /// compatibility shape provides the owning class in one preprocessor
5670    /// branch and aliases it to a standard-library type in the other branch;
5671    /// the nested fallback alias is therefore not itself active in every
5672    /// branch even though the qualified owner API is.
5673    ///
5674    /// This is deliberately narrower than ordinary type visibility.  The
5675    /// caller has already recovered a member-pointer owner path from the CST;
5676    /// this helper additionally requires the target's structured parent to
5677    /// match that path, physical source visibility, and exact preprocessor
5678    /// guard agreement with the parent declaration.  Only then may the
5679    /// parent's direct/complementary same-FQN visibility stand in for the
5680    /// nested terminal's active-branch check.
5681    pub fn dependent_member_pointer_alias_visible_in_context(
5682        &self,
5683        analyzer: &CppGraphSource<'_>,
5684        file: &ProjectFile,
5685        candidate: &CodeUnit,
5686        owner_components: &[String],
5687        reference: Node<'_>,
5688    ) -> bool {
5689        if !analyzer
5690            .type_alias_provider()
5691            .is_some_and(|provider| provider.is_type_alias(candidate))
5692        {
5693            return false;
5694        }
5695        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5696            return false;
5697        };
5698        if terminal != candidate.identifier()
5699            || canonical_cpp_scope_components(candidate) != owner_components
5700        {
5701            return false;
5702        }
5703        let Some(expected_parent_fq_name) =
5704            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5705        else {
5706            return false;
5707        };
5708        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5709            return false;
5710        };
5711        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5712            || parent_anchor.source() != candidate.source()
5713            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5714        {
5715            return false;
5716        }
5717
5718        // The ordinary path already handles unguarded aliases (and preserves
5719        // same-file declaration ordering).  This fallback is only for a
5720        // physically visible declaration whose guard is the owning branch's
5721        // guard, so reject a same-file declaration that appears after the
5722        // reference before considering guard compatibility.
5723        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5724            || candidate.source() == file
5725                && !analyzer
5726                    .ranges(candidate)
5727                    .iter()
5728                    .any(|range| range.start_byte < reference.start_byte())
5729        {
5730            return false;
5731        }
5732
5733        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5734        if candidate_guards.is_empty() {
5735            return false;
5736        }
5737        let same_guard_sets =
5738            |left: &[(usize, HashSet<PreprocessorGuard>)],
5739             right: &[(usize, HashSet<PreprocessorGuard>)]| {
5740                left.iter().all(|(_, left_guards)| {
5741                    right
5742                        .iter()
5743                        .any(|(_, right_guards)| left_guards == right_guards)
5744                })
5745            };
5746        let parent_candidates = self
5747            .visible_identifier_candidates(file, parent_anchor.identifier())
5748            .filter(|peer| {
5749                peer.kind() == parent_anchor.kind()
5750                    && peer.fq_name() == expected_parent_fq_name.as_str()
5751                    && peer.source() == parent_anchor.source()
5752                    && canonical_cpp_scope_components(peer) == owner_prefix
5753            })
5754            .filter_map(|peer| {
5755                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5756                (candidate_guards.len() == parent_guards.len()
5757                    && same_guard_sets(&candidate_guards, &parent_guards)
5758                    && same_guard_sets(&parent_guards, &candidate_guards))
5759                .then(|| (peer.clone(), parent_guards))
5760            })
5761            .collect::<Vec<_>>();
5762        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5763            return false;
5764        };
5765
5766        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5767            return false;
5768        };
5769        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5770        else {
5771            return false;
5772        };
5773        // An external header selects its declaration branch before the
5774        // reference file is parsed. Require compatible reference guards, but
5775        // do not test the header's guard expression for stability in the
5776        // reference file. Same-file aliases still require that stability.
5777        if !candidate_guards.iter().any(|(_, target_guards)| {
5778            guards_compatible_at_reference(target_guards, Some(&reference_guards))
5779                && (candidate.source() != file
5780                    || self.preprocessor_guards_stable_between(
5781                        file,
5782                        0,
5783                        reference.start_byte(),
5784                        target_guards,
5785                    ))
5786        }) {
5787            return false;
5788        }
5789
5790        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5791    }
5792
5793    /// Check a type candidate's preprocessor/import context without imposing
5794    /// ordinary declaration-before-reference ordering for same-file peers.
5795    ///
5796    /// C++ class scope makes member names visible throughout the complete
5797    /// class, including a trailing return type that appears before the member
5798    /// alias declaration in source order. Callers must first prove that the
5799    /// reference is inside the candidate's indexed class owner; this helper
5800    /// only relaxes the byte-order predicate while retaining guard and include
5801    /// activation checks.
5802    pub fn external_type_candidate_guard_compatible_in_context(
5803        &self,
5804        analyzer: &CppGraphSource<'_>,
5805        file: &ProjectFile,
5806        candidate: &CodeUnit,
5807        reference: Node<'_>,
5808    ) -> bool {
5809        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5810            return false;
5811        };
5812        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5813
5814        self.visible_identifier_candidates(file, candidate.identifier())
5815            .filter(|peer| same_logical_symbol(candidate, peer))
5816            .any(|peer| {
5817                declaration_guard_requirements(analyzer, self.cpp, peer)
5818                    .into_iter()
5819                    .any(|(declaration_byte, declaration_guards)| {
5820                        if peer.source() == file {
5821                            let (start, end) = if declaration_byte <= reference.start_byte() {
5822                                (declaration_byte, reference.start_byte())
5823                            } else {
5824                                (reference.start_byte(), declaration_byte)
5825                            };
5826                            return guard_requirements_hold_at_reference(
5827                                &declaration_guards,
5828                                reference_guards.as_ref(),
5829                            ) && self.preprocessor_guards_stable_between(
5830                                file,
5831                                start,
5832                                end,
5833                                &declaration_guards,
5834                            );
5835                        }
5836                        self.foreign_declaration_reachable_at_reference(
5837                            file,
5838                            prepared.as_ref(),
5839                            peer.source(),
5840                            &declaration_guards,
5841                            reference_guards.as_ref(),
5842                            reference.start_byte(),
5843                        )
5844                    })
5845            })
5846    }
5847
5848    /// Whether a same-file callable declaration is nameable from `reference`
5849    /// after deliberately relaxing declaration-before-reference ordering.
5850    ///
5851    /// Ordinary lookup still requires an earlier declaration. Definition
5852    /// navigation for incomplete C translation units may recover a later
5853    /// definition, but only when it is at file scope and its preprocessor
5854    /// requirements hold at the call (#2404).
5855    pub fn same_file_callable_guard_compatible_ignoring_order(
5856        &self,
5857        analyzer: &CppGraphSource<'_>,
5858        file: &ProjectFile,
5859        candidate: &CodeUnit,
5860        reference: Node<'_>,
5861    ) -> bool {
5862        if candidate.source() != file || !candidate.is_callable() {
5863            return false;
5864        }
5865        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5866            return false;
5867        };
5868        let guards = OnceCell::new();
5869        let context = CallableReferenceContext {
5870            file,
5871            position: Some(CallableReferencePosition {
5872                prepared: prepared.as_ref(),
5873                byte: reference.start_byte(),
5874                guards: &guards,
5875            }),
5876        };
5877        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
5878            .into_iter()
5879            .any(|declaration| {
5880                callable_preprocessor_context_is_visible_for_reference(
5881                    declaration,
5882                    prepared.source(),
5883                    &context,
5884                )
5885            })
5886    }
5887
5888    pub fn type_candidate_may_be_visible_before_reference(
5889        &self,
5890        analyzer: &CppGraphSource<'_>,
5891        file: &ProjectFile,
5892        candidate: &CodeUnit,
5893        reference_byte: usize,
5894    ) -> bool {
5895        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5896            return false;
5897        };
5898        let root = prepared.tree().root_node();
5899        let end_byte = reference_byte
5900            .saturating_add(1)
5901            .min(prepared.source().len());
5902        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5903            return false;
5904        };
5905        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5906    }
5907
5908    pub fn preprocessor_guards_stable_between(
5909        &self,
5910        file: &ProjectFile,
5911        start_byte: usize,
5912        end_byte: usize,
5913        guards: &HashSet<PreprocessorGuard>,
5914    ) -> bool {
5915        if guards.is_empty() || start_byte >= end_byte {
5916            return true;
5917        }
5918        let cell = self.macro_event_cell(file);
5919        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5920        let mut visited = HashSet::from_iter([file.clone()]);
5921        !events.iter().any(|event| {
5922            event.byte() >= start_byte
5923                && event.byte() < end_byte
5924                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5925        })
5926    }
5927
5928    fn macro_event_may_mutate_guards(
5929        &self,
5930        event: &MacroEvent,
5931        guards: &HashSet<PreprocessorGuard>,
5932        visited: &mut HashSet<ProjectFile>,
5933    ) -> bool {
5934        match event {
5935            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5936                guards.iter().any(|guard| guard.may_depend_on_macro(name))
5937            }
5938            MacroEvent::Include { targets, .. } => {
5939                targets.is_empty()
5940                    || targets
5941                        .iter()
5942                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
5943            }
5944            MacroEvent::Invalidate { .. } => true,
5945        }
5946    }
5947
5948    fn source_may_mutate_guards(
5949        &self,
5950        file: &ProjectFile,
5951        guards: &HashSet<PreprocessorGuard>,
5952        visited: &mut HashSet<ProjectFile>,
5953    ) -> bool {
5954        if !visited.insert(file.clone()) {
5955            return false;
5956        }
5957        let cell = self.macro_event_cell(file);
5958        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5959        events
5960            .iter()
5961            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5962    }
5963
5964    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5965        let normalized = normalize_reference_name(raw_name)?;
5966        self.type_candidates(file, &normalized)
5967            .into_iter()
5968            .next()
5969            .cloned()
5970    }
5971
5972    /// Mirror forward navigation's visible-name fallback for a bare parameter
5973    /// type after lexical owner and inheritance lookup is exhausted.
5974    ///
5975    /// Generated or otherwise unindexed base classes can hide the alias that
5976    /// makes a parameter type valid C++. Accept the fallback only when every
5977    /// include-visible class or alias with that spelling canonicalizes to one
5978    /// logical type. A shadowing local type resolves lexically before this
5979    /// path, while distinct visible types keep the result ambiguous.
5980    pub fn unique_visible_parameter_type_fallback(
5981        &self,
5982        analyzer: &CppGraphSource<'_>,
5983        file: &ProjectFile,
5984        node: Node<'_>,
5985        source: &str,
5986    ) -> Option<CodeUnit> {
5987        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5988            return None;
5989        }
5990        let name = node_text(node, source);
5991        let candidates = self
5992            .visible_identifier_candidates(file, name)
5993            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
5994            .filter(|candidate| {
5995                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
5996            })
5997            .collect::<Vec<_>>();
5998        self.unique_canonical_type_candidate(analyzer, file, &candidates)
5999    }
6000
6001    pub fn resolve_type_node_result(
6002        &self,
6003        file: &ProjectFile,
6004        node: Node<'_>,
6005        source: &str,
6006    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6007        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6008            return Ok(None);
6009        };
6010        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6011            return Ok(Some(primary));
6012        };
6013        self.resolve_template_arguments(file, primary, &arguments)
6014            .map(Some)
6015    }
6016
6017    pub fn resolve_type_node_primary(
6018        &self,
6019        file: &ProjectFile,
6020        node: Node<'_>,
6021        source: &str,
6022    ) -> Option<CodeUnit> {
6023        let components = cpp_type_name_components(node, source)?;
6024        self.resolve_type(file, &components.join("::"))
6025    }
6026
6027    pub fn resolve_template_arguments(
6028        &self,
6029        file: &ProjectFile,
6030        primary: CodeUnit,
6031        arguments: &[CppTemplateExpression],
6032    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6033        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6034    }
6035
6036    fn resolve_template_arguments_inner(
6037        &self,
6038        file: &ProjectFile,
6039        primary: CodeUnit,
6040        arguments: &[CppTemplateExpression],
6041        seen_aliases: &mut HashSet<CodeUnit>,
6042    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6043        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6044            && let Some(alias_target) = &metadata.alias_target
6045        {
6046            if !seen_aliases.insert(primary.clone()) {
6047                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6048            }
6049            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6050                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6051            let target_name = alias_target.components.join("::");
6052            let target_primary = if alias_target.global {
6053                unique_logical_type_candidate(self.type_candidates(file, &target_name))
6054            } else {
6055                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6056            };
6057            let Some(target_primary) = target_primary else {
6058                // A dependent or external RHS cannot be canonicalized from the
6059                // indexed graph. Preserve the alias's direct identity instead
6060                // of inventing a target from its source spelling.
6061                return Ok(primary);
6062            };
6063            let Some(target_arguments) = &alias_target.arguments else {
6064                return Ok(target_primary);
6065            };
6066            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6067                .ok_or(CppTemplateResolutionError::Substitution)?;
6068            return self.resolve_template_arguments_inner(
6069                file,
6070                target_primary,
6071                &target_arguments,
6072                seen_aliases,
6073            );
6074        }
6075
6076        let primary_fq_name = self
6077            .cpp_template_metadata
6078            .get(&primary)
6079            .map(|metadata| metadata.primary_fq_name.clone())
6080            .unwrap_or_else(|| primary.fq_name());
6081        let has_specialization_metadata = self
6082            .cpp_template_families
6083            .get(&primary_fq_name)
6084            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6085        if !has_specialization_metadata {
6086            return Ok(primary);
6087        }
6088        self.select_template_specialization(file, &primary, arguments)
6089    }
6090
6091    fn select_template_specialization(
6092        &self,
6093        file: &ProjectFile,
6094        resolved: &CodeUnit,
6095        explicit_arguments: &[CppTemplateExpression],
6096    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6097        let primary_fq_name = self
6098            .cpp_template_metadata
6099            .get(resolved)
6100            .map(|metadata| metadata.primary_fq_name.clone())
6101            .unwrap_or_else(|| resolved.fq_name());
6102        let family = self
6103            .cpp_template_families
6104            .get(&primary_fq_name)
6105            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6106        let primary_candidates = family
6107            .iter()
6108            .filter_map(|unit| {
6109                let metadata = self.cpp_template_metadata.get(unit)?;
6110                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6111            })
6112            .collect::<Vec<_>>();
6113        let primary_unit = primary_candidates
6114            .iter()
6115            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6116            .or_else(|| {
6117                primary_candidates
6118                    .iter()
6119                    .map(|(unit, _)| *unit)
6120                    .min_by_key(|unit| {
6121                        (
6122                            unit.source().to_string(),
6123                            unit.signature().unwrap_or_default(),
6124                        )
6125                    })
6126            })
6127            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6128        let primary_parameters =
6129            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6130                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6131        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6132            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6133
6134        let mut applicable = Vec::new();
6135        for unit in family {
6136            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6137                continue;
6138            };
6139            if metadata.is_primary() || !self.is_visible(file, unit) {
6140                continue;
6141            }
6142            if !cpp_specialization_matches(metadata, &expanded) {
6143                continue;
6144            }
6145            applicable.push((unit, metadata));
6146        }
6147        if applicable.is_empty() {
6148            return Ok(primary_unit.clone());
6149        }
6150
6151        // A scalar constraint count cannot represent C++ partial ordering:
6152        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
6153        // Select only a logical candidate whose structural pattern is strictly
6154        // more specialized than every other distinct applicable candidate.
6155        let winners = applicable
6156            .iter()
6157            .filter(|(candidate, candidate_metadata)| {
6158                applicable.iter().all(|(other, other_metadata)| {
6159                    same_visible_symbol(candidate, other)
6160                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6161                })
6162            })
6163            .copied()
6164            .collect::<Vec<_>>();
6165        let Some((selected, _)) = winners.first() else {
6166            // Mutually incomparable applicable candidates: every one of them
6167            // is a live contender.
6168            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6169                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6170            });
6171        };
6172        if winners
6173            .iter()
6174            .any(|(unit, _)| !same_visible_symbol(unit, selected))
6175        {
6176            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6177                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6178            });
6179        }
6180        Ok((*selected).clone())
6181    }
6182
6183    pub fn resolve_type_components_lexically(
6184        &self,
6185        analyzer: &CppGraphSource<'_>,
6186        file: &ProjectFile,
6187        components: &[String],
6188        global: bool,
6189        lexical_scope: &[String],
6190    ) -> LexicalTypeResolution {
6191        self.resolve_type_components_lexically_inner(
6192            analyzer,
6193            file,
6194            components,
6195            global,
6196            lexical_scope,
6197            TypeCandidateResolution::Canonical,
6198        )
6199    }
6200
6201    pub fn resolve_type_components_lexically_for_forward(
6202        &self,
6203        analyzer: &CppGraphSource<'_>,
6204        file: &ProjectFile,
6205        components: &[String],
6206        global: bool,
6207        lexical_scope: &[String],
6208    ) -> LexicalTypeResolution {
6209        self.resolve_type_components_lexically_inner(
6210            analyzer,
6211            file,
6212            components,
6213            global,
6214            lexical_scope,
6215            TypeCandidateResolution::PreserveAlias,
6216        )
6217    }
6218
6219    pub fn resolve_type_components_lexically_for_target(
6220        &self,
6221        analyzer: &CppGraphSource<'_>,
6222        file: &ProjectFile,
6223        components: &[String],
6224        global: bool,
6225        lexical_scope: &[String],
6226        target: &CodeUnit,
6227    ) -> LexicalTypeResolution {
6228        #[cfg(any(test, feature = "test-support"))]
6229        self.target_preserving_type_resolution_count
6230            .fetch_add(1, Ordering::Relaxed);
6231        self.resolve_type_components_lexically_inner(
6232            analyzer,
6233            file,
6234            components,
6235            global,
6236            lexical_scope,
6237            TypeCandidateResolution::PreserveTarget(target),
6238        )
6239    }
6240
6241    pub fn coarse_unqualified_type_reference_may_resolve(
6242        &self,
6243        file: &ProjectFile,
6244        name: &str,
6245    ) -> bool {
6246        if name.is_empty() {
6247            return true;
6248        }
6249        self.visible_identifier_candidates(file, name)
6250            .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
6251            || self.visible_parser_alias_name_is_visible(file, name)
6252    }
6253
6254    #[allow(clippy::too_many_arguments)]
6255    pub fn structured_type_reference_may_resolve_to_target(
6256        &self,
6257        analyzer: &CppGraphSource<'_>,
6258        file: &ProjectFile,
6259        components: &[String],
6260        global: bool,
6261        lexical_scope: &[String],
6262        target: &CodeUnit,
6263    ) -> bool {
6264        if components.is_empty() {
6265            return true;
6266        }
6267        let Some(terminal) = components.last() else {
6268            return true;
6269        };
6270        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6271            .map(|qualified| qualified.join("::"))
6272            .collect::<Vec<_>>();
6273        let target_name = cpp_name_for(target);
6274        if qualified_tiers
6275            .iter()
6276            .any(|qualified| qualified == &target_name)
6277        {
6278            return true;
6279        }
6280
6281        let mut saw_shape_candidate = false;
6282        for candidate in self.visible_identifier_candidates(file, terminal) {
6283            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6284            {
6285                continue;
6286            }
6287            let candidate_name = cpp_name_for(candidate);
6288            let shape_matches = if global || components.len() > 1 {
6289                qualified_tiers
6290                    .iter()
6291                    .any(|qualified| qualified == &candidate_name)
6292            } else {
6293                true
6294            };
6295            if !shape_matches {
6296                continue;
6297            }
6298            saw_shape_candidate = true;
6299            if same_visible_symbol(candidate, target)
6300                || self.c_tag_declaration_family_matches_target(
6301                    analyzer,
6302                    file,
6303                    std::slice::from_ref(&candidate),
6304                    target,
6305                )
6306                || self.compatible_primary_template_redeclarations(candidate, target)
6307                || (declared_type_alias(analyzer, candidate)
6308                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6309            {
6310                return true;
6311            }
6312        }
6313
6314        !saw_shape_candidate
6315    }
6316
6317    /// A C tag's forward declaration and complete definition are one logical
6318    /// type even when their indexed signatures and source files differ. Keep
6319    /// this identity narrow: the complete declaration must be a top-level C
6320    /// tag, every visible candidate must be its physical forward declaration,
6321    /// and the parsed tag kind must agree. Different FQNs, competing complete
6322    /// definitions, aliases, and struct/union mismatches remain ambiguous.
6323    fn cached_c_tag_kind(
6324        &self,
6325        analyzer: &CppGraphSource<'_>,
6326        candidate: &CodeUnit,
6327    ) -> Option<CppCTagKind> {
6328        if let Some(kind) = self
6329            .c_tag_kind_cache
6330            .lock()
6331            .expect("C tag kind cache poisoned")
6332            .get(candidate)
6333        {
6334            return *kind;
6335        }
6336        let kind = indexed_c_tag_kind(analyzer, candidate);
6337        self.c_tag_kind_cache
6338            .lock()
6339            .expect("C tag kind cache poisoned")
6340            .insert(candidate.clone(), kind);
6341        kind
6342    }
6343
6344    fn cached_unique_c_tag_complete_definition(
6345        &self,
6346        analyzer: &CppGraphSource<'_>,
6347        target: &CodeUnit,
6348        target_tag: CppCTagKind,
6349    ) -> Option<CodeUnit> {
6350        if let Some(definition) = self
6351            .c_tag_complete_definition_cache
6352            .lock()
6353            .expect("C tag complete-definition cache poisoned")
6354            .get(target)
6355        {
6356            return definition.clone();
6357        }
6358        let complete_definitions = analyzer
6359            .definitions(&target.fq_name())
6360            .filter(|candidate| {
6361                candidate.is_class()
6362                    && !declared_type_alias(analyzer, candidate)
6363                    && is_c_source_file(candidate.source())
6364                    && analyzer.parent_of(candidate).is_none()
6365                    && cpp_class_declaration_strength(analyzer, candidate)
6366                        == CppClassDeclarationStrength::Full
6367                    && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6368            })
6369            .collect::<HashSet<_>>();
6370        let definition = (complete_definitions.len() == 1)
6371            .then(|| complete_definitions.into_iter().next())
6372            .flatten()
6373            .filter(|candidate| same_visible_symbol(candidate, target));
6374        self.c_tag_complete_definition_cache
6375            .lock()
6376            .expect("C tag complete-definition cache poisoned")
6377            .insert(target.clone(), definition.clone());
6378        definition
6379    }
6380
6381    pub fn c_tag_declaration_family_matches_target(
6382        &self,
6383        analyzer: &CppGraphSource<'_>,
6384        visible_from: &ProjectFile,
6385        candidates: &[&CodeUnit],
6386        target: &CodeUnit,
6387    ) -> bool {
6388        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6389            let candidate_evidence = candidates
6390                .iter()
6391                .map(|candidate| {
6392                    (
6393                        candidate.fq_name(),
6394                        candidate.source().rel_path().to_path_buf(),
6395                        cpp_class_declaration_strength(analyzer, candidate),
6396                        indexed_c_tag_kind(analyzer, candidate),
6397                        self.is_physically_visible(visible_from, candidate),
6398                    )
6399                })
6400                .collect::<Vec<_>>();
6401            eprintln!(
6402                "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6403                visible_from.rel_path().display(),
6404                target.fq_name(),
6405                target.source().rel_path().display(),
6406                cpp_class_declaration_strength(analyzer, target),
6407                indexed_c_tag_kind(analyzer, target),
6408            );
6409        }
6410        if candidates.is_empty()
6411            || !target.is_class()
6412            || declared_type_alias(analyzer, target)
6413            || !is_c_source_file(target.source())
6414            || analyzer.parent_of(target).is_some()
6415            || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6416        {
6417            return false;
6418        }
6419        let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6420            return false;
6421        };
6422        if self
6423            .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6424            .is_none()
6425        {
6426            return false;
6427        }
6428        let mut saw_visible_forward = false;
6429        for candidate in candidates.iter().copied() {
6430            if candidate == target {
6431                continue;
6432            }
6433            if !candidate.is_class()
6434                || declared_type_alias(analyzer, candidate)
6435                || candidate.fq_name() != target.fq_name()
6436                || analyzer.parent_of(candidate).is_some()
6437                || cpp_class_declaration_strength(analyzer, candidate)
6438                    != CppClassDeclarationStrength::Forward
6439                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6440                || !self.is_physically_visible(visible_from, candidate)
6441            {
6442                return false;
6443            }
6444            saw_visible_forward = true;
6445        }
6446        saw_visible_forward
6447    }
6448
6449    /// Collapse one visible complete C tag and its visible forward declarations
6450    /// before ordinary lexical resolution sees their different signatures as
6451    /// competing types. A second complete definition, a different FQN/tag
6452    /// kind, or an unknown declaration shape remains ambiguous.
6453    fn unique_c_tag_declaration_family(
6454        &self,
6455        analyzer: &CppGraphSource<'_>,
6456        visible_from: &ProjectFile,
6457        candidates: &[&CodeUnit],
6458    ) -> Option<CodeUnit> {
6459        let first = candidates.first()?;
6460        let target_fq_name = first.fq_name();
6461        let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6462        let mut full = None;
6463        let mut saw_forward = false;
6464        for candidate in candidates.iter().copied() {
6465            if !candidate.is_class()
6466                || declared_type_alias(analyzer, candidate)
6467                || candidate.fq_name() != target_fq_name
6468                || analyzer.parent_of(candidate).is_some()
6469                || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6470            {
6471                return None;
6472            }
6473            match cpp_class_declaration_strength(analyzer, candidate) {
6474                CppClassDeclarationStrength::Full
6475                    if is_c_source_file(candidate.source())
6476                        && full.replace(candidate.clone()).is_none() => {}
6477                CppClassDeclarationStrength::Forward
6478                    if self.is_physically_visible(visible_from, candidate) =>
6479                {
6480                    saw_forward = true
6481                }
6482                _ => return None,
6483            }
6484        }
6485        if saw_forward { full } else { None }
6486    }
6487
6488    pub fn target_preserving_reference_namespace(
6489        &self,
6490        analyzer: &CppGraphSource<'_>,
6491        file: &ProjectFile,
6492        identifier: &str,
6493        target: &CodeUnit,
6494    ) -> Option<Vec<String>> {
6495        let mut namespace = None;
6496        for candidate in self.visible_identifier_candidates(file, identifier) {
6497            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6498            {
6499                continue;
6500            }
6501            if !(same_visible_symbol(candidate, target)
6502                || self.compatible_primary_template_redeclarations(candidate, target)
6503                || declared_type_alias(analyzer, candidate)
6504                    && self.structured_alias_primary_preserves_target(
6505                        analyzer, file, candidate, target,
6506                    ))
6507            {
6508                continue;
6509            }
6510            if namespace
6511                .as_ref()
6512                .is_some_and(|existing| existing != candidate.package_name())
6513            {
6514                return None;
6515            }
6516            namespace = Some(candidate.package_name().to_string());
6517        }
6518        let namespace = namespace?;
6519        Some(
6520            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6521                brokk_bifrost_core::analyzer::Language::Cpp,
6522                &namespace,
6523            ),
6524        )
6525    }
6526
6527    pub fn resolve_imported_type_candidate(
6528        &self,
6529        analyzer: &CppGraphSource<'_>,
6530        file: &ProjectFile,
6531        target: &CodeUnit,
6532        target_components: &[String],
6533        direct_target: Option<&CodeUnit>,
6534        preserve_alias: bool,
6535    ) -> LexicalTypeResolution {
6536        let candidates = [target];
6537        let resolution = if preserve_alias {
6538            TypeCandidateResolution::PreserveAlias
6539        } else {
6540            direct_target.map_or(
6541                TypeCandidateResolution::Canonical,
6542                TypeCandidateResolution::PreserveTarget,
6543            )
6544        };
6545        // One candidate goes in, so a failure here is never "choose one of
6546        // these": it is the alias chain leaving the index, which must answer
6547        // missing rather than ambiguous (#1828).
6548        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6549            Ok(unit) => LexicalTypeResolution::Resolved {
6550                unit,
6551                components: target_components.to_vec(),
6552                candidates: vec![target.clone()],
6553            },
6554            Err(failure) => failure.lexical_resolution(),
6555        }
6556    }
6557
6558    fn resolve_type_components_lexically_inner(
6559        &self,
6560        analyzer: &CppGraphSource<'_>,
6561        file: &ProjectFile,
6562        components: &[String],
6563        global: bool,
6564        lexical_scope: &[String],
6565        resolution: TypeCandidateResolution<'_>,
6566    ) -> LexicalTypeResolution {
6567        if components.is_empty() {
6568            return LexicalTypeResolution::Missing;
6569        }
6570        // A C++ class injects its own name into the class scope.  The indexed
6571        // FqName for that declaration is the class path itself (for example,
6572        // `n::raw_hash_set`), not a synthetic child named
6573        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
6574        // requested identifier to every scope component, so they cannot
6575        // represent that injected binding when the enclosing class is the
6576        // closest scope.  Recover the binding from the structured class path
6577        // before allowing lookup to fall through to an outer same-spelled
6578        // declaration.
6579        let mut injected = self.resolve_injected_class_name(
6580            analyzer,
6581            file,
6582            components,
6583            global,
6584            lexical_scope,
6585            resolution,
6586        );
6587        for qualified in lexical_component_tiers(components, global, lexical_scope) {
6588            let prefix_len = qualified.len().saturating_sub(components.len());
6589            if injected
6590                .as_ref()
6591                .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6592            {
6593                return injected
6594                    .take()
6595                    .expect("injected class resolution was just present")
6596                    .1;
6597            }
6598            let qualified_name = qualified.join("::");
6599            let candidates = self
6600                .type_candidates(file, &qualified_name)
6601                .into_iter()
6602                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6603                .collect::<Vec<_>>();
6604            if candidates.is_empty() {
6605                if !global && components.len() == 1 {
6606                    match self.resolve_inherited_type_for_lexical_scope(
6607                        analyzer,
6608                        file,
6609                        &qualified[..prefix_len],
6610                        &components[0],
6611                        resolution,
6612                    ) {
6613                        LexicalTypeResolution::Missing => {}
6614                        inherited => return inherited,
6615                    }
6616                }
6617                continue;
6618            }
6619            let candidates =
6620                self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6621            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6622                Ok(unit) => unit,
6623                Err(failure) => return failure.lexical_resolution(),
6624            };
6625            return LexicalTypeResolution::Resolved {
6626                unit,
6627                components: qualified,
6628                candidates: candidates.into_iter().cloned().collect(),
6629            };
6630        }
6631        LexicalTypeResolution::Missing
6632    }
6633
6634    fn resolve_injected_class_name(
6635        &self,
6636        analyzer: &CppGraphSource<'_>,
6637        file: &ProjectFile,
6638        components: &[String],
6639        global: bool,
6640        lexical_scope: &[String],
6641        resolution: TypeCandidateResolution<'_>,
6642    ) -> Option<(usize, LexicalTypeResolution)> {
6643        if global
6644            || components.len() != 1
6645            || file.rel_path().extension().is_some_and(|ext| ext == "c")
6646            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6647        {
6648            return None;
6649        }
6650        let name = components.first()?;
6651        let mut matches: Vec<&CodeUnit> = Vec::new();
6652        let mut owner_len = 0;
6653        for candidate in self.visible_identifier_candidates(file, name) {
6654            if !candidate.is_class()
6655                || declared_type_alias(analyzer, candidate)
6656                || candidate.identifier() != name
6657            {
6658                continue;
6659            }
6660            let candidate_scope = canonical_cpp_scope_components(candidate);
6661            if candidate_scope.len() > lexical_scope.len()
6662                || !lexical_scope.starts_with(&candidate_scope)
6663                || candidate_scope.last().is_none_or(|last| last != name)
6664            {
6665                continue;
6666            }
6667            if candidate_scope.len() > owner_len {
6668                owner_len = candidate_scope.len();
6669                matches.clear();
6670            }
6671            if candidate_scope.len() == owner_len
6672                && !matches
6673                    .iter()
6674                    .any(|existing| same_logical_symbol(existing, candidate))
6675            {
6676                matches.push(candidate);
6677            }
6678        }
6679        if matches.is_empty() {
6680            return None;
6681        }
6682        // The exact current class boundary is the most important injected-name
6683        // case. Ordinary lexical lookup appends the requested name and can
6684        // otherwise select a synthetic constructor-shaped child such as
6685        // `Portal::Impl::Impl` instead of the enclosing `Portal::Impl` class.
6686        // The global and single-component gates above keep qualified receiver
6687        // and static-qualifier contexts out of this recovery.
6688        let owner_components = lexical_scope[..owner_len].to_vec();
6689        let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6690        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6691            Ok(unit) => LexicalTypeResolution::Resolved {
6692                unit,
6693                components: owner_components,
6694                candidates: matches.into_iter().cloned().collect(),
6695            },
6696            Err(failure) => failure.lexical_resolution(),
6697        };
6698        Some((owner_len, resolution))
6699    }
6700
6701    fn resolve_inherited_type_for_lexical_scope(
6702        &self,
6703        analyzer: &CppGraphSource<'_>,
6704        file: &ProjectFile,
6705        lexical_scope: &[String],
6706        name: &str,
6707        resolution: TypeCandidateResolution<'_>,
6708    ) -> LexicalTypeResolution {
6709        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6710            return LexicalTypeResolution::Missing;
6711        };
6712        let lexical_owner_name = lexical_scope.join("::");
6713        if lexical_owner_name.is_empty() {
6714            return LexicalTypeResolution::Missing;
6715        }
6716        let owner_candidates = self
6717            .type_candidates(file, &lexical_owner_name)
6718            .into_iter()
6719            .filter(|candidate| {
6720                canonical_cpp_name_matches(candidate, &lexical_owner_name)
6721                    && !declared_type_alias(analyzer, candidate)
6722            })
6723            .collect::<Vec<_>>();
6724        if owner_candidates.is_empty() {
6725            return LexicalTypeResolution::Missing;
6726        }
6727        // A visible forward declaration and the physical class definition share
6728        // one FQN, but only the definition owns hierarchy facts. When lookup is
6729        // physically inside that definition, do not let an earlier header
6730        // forward declaration erase its base edges (#2240).
6731        let physical_owner_candidates = owner_candidates
6732            .iter()
6733            .copied()
6734            .filter(|candidate| candidate.source() == file)
6735            .collect::<Vec<_>>();
6736        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6737            owner_candidates
6738        } else {
6739            physical_owner_candidates
6740        };
6741        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6742            return LexicalTypeResolution::Ambiguous;
6743        };
6744
6745        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6746        let mut visited_owners = HashSet::default();
6747        while !frontier.is_empty() {
6748            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6749            let mut next_frontier = Vec::new();
6750            for owner in frontier {
6751                if !visited_owners.insert(owner.fq_name()) {
6752                    continue;
6753                }
6754                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6755                let candidates = self
6756                    .type_candidates(file, &qualified_name)
6757                    .into_iter()
6758                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6759                    .collect::<Vec<_>>();
6760                if candidates.is_empty() {
6761                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
6762                        if !next_frontier
6763                            .iter()
6764                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6765                        {
6766                            next_frontier.push(ancestor);
6767                        }
6768                    }
6769                    continue;
6770                }
6771                let candidates =
6772                    self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6773                let unit =
6774                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6775                        Ok(unit) => unit,
6776                        Err(failure) => return failure.lexical_resolution(),
6777                    };
6778                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6779            }
6780            if let Some((unit, candidates)) = level_matches.first().cloned() {
6781                let Some(first_declaration) = candidates.first() else {
6782                    return LexicalTypeResolution::Ambiguous;
6783                };
6784                if !level_matches.iter().all(|(_, declarations)| {
6785                    declarations
6786                        .iter()
6787                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
6788                }) {
6789                    return LexicalTypeResolution::Ambiguous;
6790                }
6791                let mut components = lexical_scope.to_vec();
6792                components.push(name.to_string());
6793                return LexicalTypeResolution::Resolved {
6794                    unit,
6795                    components,
6796                    candidates,
6797                };
6798            }
6799            frontier = next_frontier;
6800        }
6801        LexicalTypeResolution::Missing
6802    }
6803
6804    /// Resolve a base class through its injected class name at the nearest
6805    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
6806    ///
6807    /// A base whose canonical full definition cannot be pinned from `file` -
6808    /// a forward declaration the include closure completes with two different
6809    /// full definitions, or an alias chain that leaves the index - stops the
6810    /// walk only when that base is spelled `injected_name`. The mem-initializer
6811    /// names a base by that base's own injected class name, so a base spelled
6812    /// differently can never be the one it names, whichever definition it would
6813    /// have turned out to be; aborting the level on its account instead loses
6814    /// the sibling base that *is* named (#2543). A base that is spelled
6815    /// `injected_name` still fails closed, because choosing a deeper same-named
6816    /// ancestor over it would bind the initializer to the wrong constructor.
6817    /// The skipped base carries its own ancestors out of the walk with it: with
6818    /// no canonical unit, the repeated-base accounting below cannot tell one
6819    /// inherited path through it from two.
6820    pub fn inherited_injected_class_owner(
6821        &self,
6822        analyzer: &CppGraphSource<'_>,
6823        file: &ProjectFile,
6824        enclosing_owner: &CodeUnit,
6825        injected_name: &str,
6826    ) -> Option<CodeUnit> {
6827        let hierarchy = analyzer.type_hierarchy_provider()?;
6828        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6829        let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6830        while !frontier.is_empty() {
6831            let mut level_matches = Vec::new();
6832            let mut next_frontier = Vec::new();
6833            for raw_owner in frontier {
6834                let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6835                else {
6836                    if raw_owner.identifier() == injected_name {
6837                        return None;
6838                    }
6839                    continue;
6840                };
6841                let propagated = propagated_counts.entry(owner.clone()).or_default();
6842                if *propagated == 2 {
6843                    continue;
6844                }
6845                *propagated += 1;
6846                if owner.identifier() == injected_name {
6847                    level_matches.push(owner.clone());
6848                }
6849                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6850            }
6851            match level_matches.as_slice() {
6852                [owner] => return Some(owner.clone()),
6853                [_, ..] => return None,
6854                [] => {}
6855            }
6856            frontier = next_frontier;
6857        }
6858        None
6859    }
6860
6861    /// The one type the candidates name under `resolution`, or why they do not
6862    /// name one. The two preserving modes only ever reject candidates that
6863    /// disagree with each other, which is ambiguity; canonicalization can also
6864    /// fail because the alias chain leaves the index (#1828).
6865    fn resolve_type_candidates(
6866        &self,
6867        analyzer: &CppGraphSource<'_>,
6868        file: &ProjectFile,
6869        candidates: &[&CodeUnit],
6870        resolution: TypeCandidateResolution<'_>,
6871    ) -> Result<CodeUnit, TypeCandidateFailure> {
6872        if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6873            && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6874        {
6875            return Ok(unit);
6876        }
6877        match resolution {
6878            TypeCandidateResolution::Canonical => {
6879                self.canonical_type_candidate_resolution(analyzer, file, candidates)
6880            }
6881            TypeCandidateResolution::PreserveAlias => {
6882                // A generated index can retain identical alias spellings from
6883                // mutually exclusive headers. When the reference file
6884                // physically reaches exactly one of those source declarations,
6885                // include closure is the structured evidence that selects it;
6886                // treating the two source spellings as an overload set makes a
6887                // reachable alias appear ambiguous (#1844).
6888                let same_fqn_alias_family = candidates.len() > 1
6889                    && candidates.iter().all(|candidate| {
6890                        declared_type_alias(analyzer, candidate)
6891                            && same_logical_symbol(candidates[0], candidate)
6892                    })
6893                    && candidates
6894                        .iter()
6895                        .any(|candidate| candidate.source() != candidates[0].source());
6896                if same_fqn_alias_family {
6897                    let physically_visible = candidates
6898                        .iter()
6899                        .copied()
6900                        .filter(|candidate| self.is_physically_visible(file, candidate))
6901                        .collect::<Vec<_>>();
6902                    // The family is one logical declaration only when the
6903                    // reachable spellings agree. Two same-FQN aliases whose
6904                    // written targets differ (`using Choice = Canonical;` in
6905                    // one header, `using Choice = ::Canonical;` in another)
6906                    // are a genuine conflict, and choosing the first indexed
6907                    // one silently binds the reference to an arbitrary owner
6908                    // (#2398). Collapse only a single reachable declaration
6909                    // or reachable declarations with one structured target;
6910                    // everything else stays ambiguous below.
6911                    let one_structured_target = physically_visible.len() > 1
6912                        && physically_visible.iter().skip(1).all(|candidate| {
6913                            let target = self.structured_alias_target(analyzer, candidate);
6914                            target.is_some()
6915                                && target
6916                                    == self.structured_alias_target(analyzer, physically_visible[0])
6917                        });
6918                    if physically_visible.len() == 1 || one_structured_target {
6919                        return Ok(physically_visible[0].clone());
6920                    }
6921                }
6922                unique_type_candidate_preserving_alias(analyzer, file, candidates)
6923                    .ok_or(TypeCandidateFailure::Ambiguous)
6924            }
6925            TypeCandidateResolution::PreserveTarget(target) => self
6926                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6927                .ok_or(TypeCandidateFailure::Ambiguous),
6928        }
6929    }
6930
6931    fn candidates_for_type_resolution<'b>(
6932        &self,
6933        analyzer: &CppGraphSource<'_>,
6934        file: &ProjectFile,
6935        candidates: &[&'b CodeUnit],
6936        resolution: TypeCandidateResolution<'_>,
6937    ) -> Vec<&'b CodeUnit> {
6938        if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6939            let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6940            if compile_proven.len() == 1 {
6941                return compile_proven;
6942            }
6943        }
6944        candidates.to_vec()
6945    }
6946
6947    /// Narrow a same-name forward lookup to the declaration selected by the
6948    /// translation unit's compile command. The lexical resolver intentionally
6949    /// does not receive a reference node, so this is the only compile-context
6950    /// evidence available at that stage. A single selected candidate is safe:
6951    /// the caller still checks include activation and the reference's own
6952    /// guards before reporting the result as visible.
6953    fn compile_proven_type_candidates<'b>(
6954        &self,
6955        analyzer: &CppGraphSource<'_>,
6956        file: &ProjectFile,
6957        candidates: &[&'b CodeUnit],
6958    ) -> Vec<&'b CodeUnit> {
6959        let proven = self.compile_proven_guards(file);
6960        if proven.is_empty() {
6961            return Vec::new();
6962        }
6963        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6964            return Vec::new();
6965        };
6966        candidates
6967            .iter()
6968            .copied()
6969            .filter(|candidate| {
6970                let declaration_guards =
6971                    declaration_guard_requirements(analyzer, self.cpp, candidate);
6972                if declaration_guards.is_empty() {
6973                    return false;
6974                }
6975                if candidate.source() == file {
6976                    return declaration_guards.iter().any(|(_, required)| {
6977                        guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6978                    });
6979                }
6980                declaration_guards.iter().any(|(_, required)| {
6981                    self.foreign_declaration_reachable_from_compile_proven_guards(
6982                        file,
6983                        prepared.as_ref(),
6984                        candidate.source(),
6985                        required,
6986                        usize::MAX,
6987                    )
6988                })
6989            })
6990            .collect()
6991    }
6992
6993    pub fn resolve_callable_value_components_lexically(
6994        &self,
6995        analyzer: &CppGraphSource<'_>,
6996        file: &ProjectFile,
6997        owner_components: &[String],
6998        member_name: &str,
6999        global: bool,
7000        lexical_scope: &[String],
7001    ) -> LexicalCallableValueResolution {
7002        if owner_components.is_empty() || member_name.is_empty() {
7003            return LexicalCallableValueResolution::Missing;
7004        }
7005        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7006            let owner_name = qualified_owner.join("::");
7007            let type_candidates = self
7008                .type_candidates(file, &owner_name)
7009                .into_iter()
7010                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7011                .collect::<Vec<_>>();
7012            let resolved_type = if type_candidates.is_empty() {
7013                None
7014            } else {
7015                let Some(unit) =
7016                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7017                else {
7018                    return LexicalCallableValueResolution::Ambiguous;
7019                };
7020                Some(unit)
7021            };
7022
7023            let mut qualified_callable = qualified_owner;
7024            qualified_callable.push(member_name.to_string());
7025            let callable_name = qualified_callable.join("::");
7026            let free_function = self
7027                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7028                .into_iter()
7029                .find(|candidate| {
7030                    canonical_cpp_name_matches(candidate, &callable_name)
7031                        && type_owner_of(analyzer, candidate).is_none()
7032                })
7033                .cloned();
7034
7035            match (resolved_type, free_function) {
7036                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7037                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7038                (None, Some(function)) => {
7039                    return LexicalCallableValueResolution::FreeFunction(function);
7040                }
7041                (None, None) => {}
7042            }
7043        }
7044        LexicalCallableValueResolution::Missing
7045    }
7046
7047    fn resolve_type_for_declaration(
7048        &self,
7049        visible_from: &ProjectFile,
7050        declaration: &CodeUnit,
7051        raw_name: &str,
7052    ) -> Option<CodeUnit> {
7053        let normalized = normalize_reference_name(raw_name)?;
7054        if !normalized.contains("::")
7055            && let Some(namespace) = cpp_namespace_for(declaration)
7056        {
7057            for prefix in namespace_prefixes(&namespace) {
7058                let qualified = format!("{prefix}::{normalized}");
7059                if let Some(unit) = self
7060                    .type_candidates(visible_from, &qualified)
7061                    .into_iter()
7062                    .next()
7063                {
7064                    return Some(unit.clone());
7065                }
7066            }
7067        }
7068        self.resolve_type(visible_from, raw_name)
7069    }
7070
7071    fn resolve_unique_canonical_type_for_declaration(
7072        &self,
7073        analyzer: &CppGraphSource<'_>,
7074        visible_from: &ProjectFile,
7075        declaration: &CodeUnit,
7076        raw_name: &str,
7077    ) -> Option<CodeUnit> {
7078        let mut current = self.resolve_defining_type_for_declaration(
7079            analyzer,
7080            visible_from,
7081            declaration,
7082            raw_name,
7083        )?;
7084        let mut seen_aliases = HashSet::default();
7085        loop {
7086            let Some(target) = self.structured_alias_target(analyzer, &current) else {
7087                return current.is_class().then_some(current);
7088            };
7089            if matches!(target, StructuredAliasTarget::Builtin) {
7090                return current.is_class().then_some(current);
7091            }
7092            if !seen_aliases.insert(current.clone()) {
7093                return None;
7094            }
7095            current = self.resolve_structured_alias_target(visible_from, &current, &target)?;
7096        }
7097    }
7098
7099    pub fn canonical_type_unit(
7100        &self,
7101        analyzer: &CppGraphSource<'_>,
7102        visible_from: &ProjectFile,
7103        unit: &CodeUnit,
7104    ) -> Option<CodeUnit> {
7105        self.canonical_type_resolution(analyzer, visible_from, unit)
7106            .ok()
7107    }
7108
7109    /// Follow an alias only when it is visible at `reference`.
7110    ///
7111    /// The consumer need not spell the alias target. In particular, a
7112    /// conditional include can make `PublicPtr` visible at the reference while
7113    /// ordinary physical-include visibility is false. Prove the public alias
7114    /// with the reference's guard environment, then use the existing structured
7115    /// alias-chain resolver over the consumer's bounded declaration index.
7116    pub fn canonical_type_unit_in_context(
7117        &self,
7118        analyzer: &CppGraphSource<'_>,
7119        visible_from: &ProjectFile,
7120        reference: Node<'_>,
7121        unit: &CodeUnit,
7122    ) -> Option<CodeUnit> {
7123        if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7124        {
7125            return None;
7126        }
7127        self.canonical_type_resolution(analyzer, visible_from, unit)
7128            .ok()
7129    }
7130
7131    /// Follow `unit`'s alias chain to the class it names, or report why the
7132    /// chain does not end at one indexed class.
7133    ///
7134    /// A chain that leaves the index - an alias to a template parameter, to a
7135    /// standard-library type, or to any other declaration the workspace does
7136    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
7137    /// there is still nothing to choose between.
7138    fn canonical_type_resolution(
7139        &self,
7140        analyzer: &CppGraphSource<'_>,
7141        visible_from: &ProjectFile,
7142        unit: &CodeUnit,
7143    ) -> Result<CodeUnit, TypeCandidateFailure> {
7144        let mut current = unit.clone();
7145        let mut seen_aliases = HashSet::default();
7146        loop {
7147            let Some(target) = self.structured_alias_target(analyzer, &current) else {
7148                return current
7149                    .is_class()
7150                    .then_some(current)
7151                    .ok_or(TypeCandidateFailure::Unresolvable);
7152            };
7153            if matches!(target, StructuredAliasTarget::Builtin) {
7154                return current
7155                    .is_class()
7156                    .then_some(current)
7157                    .ok_or(TypeCandidateFailure::Unresolvable);
7158            }
7159            if !seen_aliases.insert(current.clone()) {
7160                return Err(TypeCandidateFailure::Unresolvable);
7161            }
7162            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
7163        }
7164    }
7165
7166    pub fn canonical_visible_full_type_unit(
7167        &self,
7168        analyzer: &CppGraphSource<'_>,
7169        visible_from: &ProjectFile,
7170        unit: &CodeUnit,
7171    ) -> Option<CodeUnit> {
7172        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7173        if cpp_class_declaration_strength(analyzer, &canonical)
7174            != CppClassDeclarationStrength::Forward
7175        {
7176            return Some(canonical);
7177        }
7178        let mut full = Vec::new();
7179        for candidate in self
7180            .visible_identifier_candidates(visible_from, canonical.identifier())
7181            .filter(|candidate| {
7182                candidate.is_class()
7183                    && candidate.fq_name() == canonical.fq_name()
7184                    && cpp_class_declaration_strength(analyzer, candidate)
7185                        == CppClassDeclarationStrength::Full
7186            })
7187        {
7188            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7189                full.push(candidate.clone());
7190            }
7191        }
7192        match full.len() {
7193            0 => Some(canonical),
7194            1 => full.pop(),
7195            _ => None,
7196        }
7197    }
7198
7199    fn resolve_structured_alias_target(
7200        &self,
7201        visible_from: &ProjectFile,
7202        declaration: &CodeUnit,
7203        target: &StructuredAliasTarget,
7204    ) -> Option<CodeUnit> {
7205        self.structured_alias_target_resolution(visible_from, declaration, target)
7206            .ok()
7207    }
7208
7209    fn structured_alias_target_resolution(
7210        &self,
7211        visible_from: &ProjectFile,
7212        declaration: &CodeUnit,
7213        target: &StructuredAliasTarget,
7214    ) -> Result<CodeUnit, TypeCandidateFailure> {
7215        let primary =
7216            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7217        let StructuredAliasTarget::Named { arguments, .. } = target else {
7218            return Err(TypeCandidateFailure::Unresolvable);
7219        };
7220        match arguments {
7221            Some(arguments) => self
7222                .resolve_template_arguments(visible_from, primary, arguments)
7223                .map_err(|error| match error {
7224                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7225                        TypeCandidateFailure::Ambiguous
7226                    }
7227                    _ => TypeCandidateFailure::Unresolvable,
7228                }),
7229            None => Ok(primary),
7230        }
7231    }
7232
7233    fn resolve_structured_alias_primary(
7234        &self,
7235        visible_from: &ProjectFile,
7236        declaration: &CodeUnit,
7237        target: &StructuredAliasTarget,
7238    ) -> Option<CodeUnit> {
7239        self.structured_alias_primary_resolution(visible_from, declaration, target)
7240            .ok()
7241    }
7242
7243    fn structured_alias_primary_resolution(
7244        &self,
7245        visible_from: &ProjectFile,
7246        declaration: &CodeUnit,
7247        target: &StructuredAliasTarget,
7248    ) -> Result<CodeUnit, TypeCandidateFailure> {
7249        let StructuredAliasTarget::Named {
7250            components, global, ..
7251        } = target
7252        else {
7253            return Err(TypeCandidateFailure::Unresolvable);
7254        };
7255        let qualified = components.join("::");
7256        let candidates = if *global {
7257            // `::A::B` anchors at the root scope, so a candidate whose
7258            // canonical path merely ends with the spelled components does not
7259            // qualify. Without this filter a global `::Canonical` target also
7260            // collects `alpha::Canonical`, the lookup reports a false
7261            // ambiguity, and the alias arm silently drops out of its
7262            // conflicting family instead of proving the conflict (#2398).
7263            let mut candidates = self.type_candidates(visible_from, &qualified);
7264            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7265            candidates
7266        } else {
7267            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7268        };
7269        logical_type_candidate(candidates)
7270    }
7271
7272    pub fn structured_alias_primary_preserves_target(
7273        &self,
7274        analyzer: &CppGraphSource<'_>,
7275        visible_from: &ProjectFile,
7276        candidate: &CodeUnit,
7277        target: &CodeUnit,
7278    ) -> bool {
7279        let mut current = candidate.clone();
7280        let mut seen = HashSet::default();
7281        let mut matched_target = false;
7282        loop {
7283            if same_visible_symbol(&current, target)
7284                || self.compatible_primary_template_redeclarations(&current, target)
7285            {
7286                matched_target = true;
7287            }
7288            if !seen.insert(current.clone()) {
7289                return false;
7290            }
7291            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7292                return matched_target;
7293            };
7294            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7295                return matched_target;
7296            };
7297            let Some(primary) =
7298                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7299            else {
7300                // A dependent member target such as `Detector<T>::type`
7301                // cannot be reduced to an indexed primary, but a preceding
7302                // structured alias hop may already have proven the requested
7303                // alias identity. Cycles still resolve a primary and are
7304                // rejected by `seen` above.
7305                return matched_target;
7306            };
7307            current = primary;
7308        }
7309    }
7310
7311    pub fn structured_class_alias_resolves_to_target(
7312        &self,
7313        analyzer: &CppGraphSource<'_>,
7314        visible_from: &ProjectFile,
7315        alias: &CodeUnit,
7316        target: &CodeUnit,
7317    ) -> bool {
7318        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7319            return false;
7320        };
7321        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7322            return false;
7323        };
7324        let StructuredAliasTarget::Named {
7325            components, global, ..
7326        } = &alias_target
7327        else {
7328            return false;
7329        };
7330        let lexical_scope = canonical_cpp_scope_components(&owner);
7331        match self.resolve_type_components_lexically_for_target(
7332            analyzer,
7333            visible_from,
7334            components,
7335            *global,
7336            &lexical_scope,
7337            target,
7338        ) {
7339            LexicalTypeResolution::Resolved {
7340                unit, candidates, ..
7341            } => {
7342                same_visible_symbol(&unit, target)
7343                    || self.same_template_member_identity(analyzer, &unit, target)
7344                    || candidates.iter().any(|candidate| {
7345                        same_visible_symbol(candidate, target)
7346                            || self.same_template_member_identity(analyzer, candidate, target)
7347                    })
7348            }
7349            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7350                self.structured_alias_primary_preserves_target(
7351                    analyzer,
7352                    visible_from,
7353                    alias,
7354                    target,
7355                ) || self.flattened_macro_namespace_alias_target_matches(
7356                    analyzer,
7357                    visible_from,
7358                    alias,
7359                    &alias_target,
7360                    target,
7361                )
7362            }
7363        }
7364    }
7365
7366    /// Return true when a class-owned alias names the requested type as one
7367    /// structured qualifier in its target path.
7368    ///
7369    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
7370    /// indexed class. Forward lookup can still retain `Primary` as its bounded
7371    /// canonical identity. Inverse lookup needs the same evidence when later
7372    /// references use only the alias spelling.
7373    pub fn structured_class_alias_path_preserves_target(
7374        &self,
7375        analyzer: &CppGraphSource<'_>,
7376        visible_from: &ProjectFile,
7377        alias: &CodeUnit,
7378        target: &CodeUnit,
7379    ) -> bool {
7380        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7381            return false;
7382        };
7383        let Some(StructuredAliasTarget::Named {
7384            components, global, ..
7385        }) = self.structured_alias_target(analyzer, alias)
7386        else {
7387            return false;
7388        };
7389        let lexical_scope = canonical_cpp_scope_components(&owner);
7390        (1..components.len()).rev().any(|component_count| {
7391            matches!(
7392                self.resolve_type_components_lexically_for_target(
7393                    analyzer,
7394                    visible_from,
7395                    &components[..component_count],
7396                    global,
7397                    &lexical_scope,
7398                    target,
7399                ),
7400                LexicalTypeResolution::Resolved {
7401                    ref unit,
7402                    ref candidates,
7403                    ..
7404                } if same_visible_symbol(unit, target)
7405                    || self.same_template_member_identity(analyzer, unit, target)
7406                    || candidates.iter().any(|candidate| {
7407                        same_visible_symbol(candidate, target)
7408                            || self.same_template_member_identity(analyzer, candidate, target)
7409                    })
7410            )
7411        })
7412    }
7413
7414    fn flattened_macro_namespace_alias_target_matches(
7415        &self,
7416        analyzer: &CppGraphSource<'_>,
7417        visible_from: &ProjectFile,
7418        alias: &CodeUnit,
7419        alias_target: &StructuredAliasTarget,
7420        target: &CodeUnit,
7421    ) -> bool {
7422        let StructuredAliasTarget::Named {
7423            components,
7424            global: false,
7425            arguments: None,
7426        } = alias_target
7427        else {
7428            return false;
7429        };
7430        let Some((target_name, namespace_components)) = components.split_last() else {
7431            return false;
7432        };
7433        if namespace_components.is_empty()
7434            || target_name != target.identifier()
7435            || alias.source() != target.source()
7436            || alias.source() != visible_from
7437            || !target.is_class()
7438            || declared_type_alias(analyzer, target)
7439        {
7440            return false;
7441        }
7442        if self
7443            .resolve_structured_alias_target(visible_from, alias, alias_target)
7444            .is_some()
7445        {
7446            return false;
7447        }
7448
7449        let alias_ranges = analyzer.ranges(alias);
7450        let target_ranges = analyzer.ranges(target);
7451        if alias_ranges.is_empty() || target_ranges.is_empty() {
7452            return false;
7453        }
7454        let alias_start = alias_ranges
7455            .iter()
7456            .map(|range| range.start_byte)
7457            .min()
7458            .expect("non-empty alias ranges have a minimum");
7459        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
7460            return false;
7461        };
7462        let root = prepared.tree().root_node();
7463        let has_matching_declaration = target_ranges
7464            .iter()
7465            .filter(|range| range.end_byte <= alias_start)
7466            .filter_map(|range| node_for_exact_range(root, range))
7467            .any(|node| {
7468                flattened_macro_namespace_components(node, prepared.source())
7469                    .is_some_and(|recovered| recovered == namespace_components)
7470            });
7471        if !has_matching_declaration {
7472            return false;
7473        }
7474
7475        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7476        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7477        guard_requirement_sets_match(&alias_guards, &target_guards)
7478    }
7479
7480    pub fn template_alias_arguments_preserve_target(
7481        &self,
7482        analyzer: &CppGraphSource<'_>,
7483        visible_from: &ProjectFile,
7484        alias: &CodeUnit,
7485        arguments: &[CppTemplateExpression],
7486        target: &CodeUnit,
7487    ) -> bool {
7488        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7489            return false;
7490        };
7491        if metadata.alias_target.is_none()
7492            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7493        {
7494            return false;
7495        }
7496        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7497    }
7498
7499    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7500        self.cpp_template_metadata
7501            .get(unit)
7502            .is_some_and(CppTemplateMetadata::is_primary)
7503    }
7504
7505    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7506        self.cpp_template_metadata
7507            .get(unit)
7508            .is_some_and(CppTemplateMetadata::is_specialization)
7509    }
7510
7511    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7512        same_visible_symbol(left, right)
7513            || self.compatible_primary_template_redeclarations(left, right)
7514    }
7515
7516    pub fn same_template_member_identity(
7517        &self,
7518        analyzer: &CppGraphSource<'_>,
7519        left: &CodeUnit,
7520        right: &CodeUnit,
7521    ) -> bool {
7522        if same_visible_symbol(left, right) {
7523            return true;
7524        }
7525        if left.kind() != right.kind()
7526            || left.identifier() != right.identifier()
7527            || left.signature() != right.signature()
7528        {
7529            return false;
7530        }
7531        let (Some(left_owner), Some(right_owner)) =
7532            (analyzer.parent_of(left), analyzer.parent_of(right))
7533        else {
7534            return false;
7535        };
7536        left_owner.is_class()
7537            && right_owner.is_class()
7538            && self.same_template_owner_identity(&left_owner, &right_owner)
7539    }
7540
7541    fn unique_canonical_type_candidate(
7542        &self,
7543        analyzer: &CppGraphSource<'_>,
7544        visible_from: &ProjectFile,
7545        candidates: &[&CodeUnit],
7546    ) -> Option<CodeUnit> {
7547        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7548            .ok()
7549    }
7550
7551    fn canonical_type_candidate_resolution(
7552        &self,
7553        analyzer: &CppGraphSource<'_>,
7554        visible_from: &ProjectFile,
7555        candidates: &[&CodeUnit],
7556    ) -> Result<CodeUnit, TypeCandidateFailure> {
7557        let mut canonical = Vec::new();
7558        for candidate in candidates {
7559            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7560            if canonical
7561                .iter()
7562                .any(|existing| same_visible_symbol(existing, &resolved))
7563            {
7564                continue;
7565            }
7566            if let Some(existing) = canonical.iter_mut().find(|existing| {
7567                self.compatible_primary_template_redeclarations(existing, &resolved)
7568            }) {
7569                // A forward declaration and its full primary-template
7570                // definition are one C++ type even when they live in
7571                // different headers and alpha-rename their parameters. The
7572                // target-preserving path already reconciles this family; do
7573                // the same for ordinary canonical lookup so an out-of-line
7574                // member's lexical owner is not made ambiguous by its own
7575                // forward declaration. Retain the strongest physical
7576                // declaration for later owner/range queries.
7577                if matches!(
7578                    (
7579                        cpp_class_declaration_strength(analyzer, existing),
7580                        cpp_class_declaration_strength(analyzer, &resolved),
7581                    ),
7582                    (
7583                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7584                        CppClassDeclarationStrength::Full,
7585                    ) | (
7586                        CppClassDeclarationStrength::Unknown,
7587                        CppClassDeclarationStrength::Forward,
7588                    )
7589                ) {
7590                    *existing = resolved;
7591                }
7592                continue;
7593            }
7594            canonical.push(resolved);
7595            if canonical.len() > 1 {
7596                return Err(TypeCandidateFailure::Ambiguous);
7597            }
7598        }
7599        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7600    }
7601
7602    pub fn unique_type_candidate_preserving_target(
7603        &self,
7604        analyzer: &CppGraphSource<'_>,
7605        visible_from: &ProjectFile,
7606        candidates: &[&CodeUnit],
7607        target: &CodeUnit,
7608    ) -> Option<CodeUnit> {
7609        // C++ headers often expose one logical type through mutually exclusive
7610        // physical declarations, for example a class in the fallback branch
7611        // and a `using` alias to the standard-library type in the configured
7612        // branch. The index intentionally retains both declarations so forward
7613        // lookup can report each target. Preserve the requested target when
7614        // that is the only ambiguity: every candidate has the same type kind,
7615        // exact canonical FQN, and source file, and the requested declaration
7616        // itself is one of the physical candidates. Do not merge same-named
7617        // declarations from different files or namespaces; those remain
7618        // ambiguous and fail closed below.
7619        if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7620            || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7621        {
7622            return Some(target.clone());
7623        }
7624        let mut resolved_candidates = Vec::new();
7625        for candidate in candidates {
7626            // An ifdef branch that aliases an unindexed system type (for
7627            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
7628            // canonicalized. That branch does not name `target`. Dropping it
7629            // keeps the branch that does. Failing the whole family here would
7630            // deny every usage of the reachable spelling (#2368).
7631            let Some(resolved) =
7632                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7633            else {
7634                continue;
7635            };
7636            if resolved_candidates
7637                .iter()
7638                .any(|existing| same_visible_symbol(existing, &resolved))
7639            {
7640                continue;
7641            }
7642            resolved_candidates.push(resolved);
7643        }
7644        match resolved_candidates.as_slice() {
7645            [] => None,
7646            [single] => Some(single.clone()),
7647            // The branches disagree about what the name aliases. When they are
7648            // spellings of one entity (#1845) that disagreement is a build
7649            // configuration, not a choice between types, so it must not deny
7650            // the requested target its reference.
7651            _ => self
7652                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7653                .map(|_| target.clone()),
7654        }
7655    }
7656
7657    /// The declaration a same-file same-FQN family stands for when a reference
7658    /// names `target`, or `None` when the candidates are not one family or the
7659    /// family does not name `target`.
7660    ///
7661    /// A translation unit cannot hold two different types under one qualified
7662    /// name, so several same-kind declarations of one FQN in one file are
7663    /// alternate spellings of one entity - the configuration branches of an
7664    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
7665    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
7666    /// targets differ; canonicalizing each branch on its own and then demanding
7667    /// agreement reports an ambiguity that denies every declaration in the
7668    /// family its usages (#1845). The family names `target` when it declares
7669    /// it, or when one branch's alias chain reaches it.
7670    ///
7671    /// Declarations in different files or namespaces are distinct entities and
7672    /// are deliberately excluded: their disagreement is a real ambiguity.
7673    pub fn same_fqn_type_spelling_for_target<'b>(
7674        &self,
7675        analyzer: &CppGraphSource<'_>,
7676        visible_from: &ProjectFile,
7677        candidates: &[&'b CodeUnit],
7678        target: &CodeUnit,
7679    ) -> Option<&'b CodeUnit> {
7680        let [first, rest @ ..] = candidates else {
7681            return None;
7682        };
7683        if rest.is_empty()
7684            || !rest.iter().all(|candidate| {
7685                candidate.kind() == first.kind()
7686                    && candidate.fq_name() == first.fq_name()
7687                    && candidate.source() == first.source()
7688            })
7689        {
7690            return None;
7691        }
7692        candidates
7693            .iter()
7694            .copied()
7695            .find(|candidate| same_symbol(candidate, target))
7696            .or_else(|| {
7697                candidates.iter().copied().find(|candidate| {
7698                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7699                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7700                })
7701            })
7702    }
7703
7704    pub fn alternate_same_fqn_type_declarations(
7705        &self,
7706        analyzer: &CppGraphSource<'_>,
7707        candidates: &[&CodeUnit],
7708        target: &CodeUnit,
7709    ) -> bool {
7710        let Some(first) = candidates.first() else {
7711            return false;
7712        };
7713        let same_api = first.kind() == target.kind()
7714            && first.fq_name() == target.fq_name()
7715            && first.source() == target.source()
7716            && candidates.iter().all(|candidate| {
7717                candidate.kind() == target.kind()
7718                    && candidate.fq_name() == target.fq_name()
7719                    && candidate.source() == target.source()
7720            })
7721            && candidates
7722                .iter()
7723                .any(|candidate| same_symbol(candidate, target))
7724            && candidates
7725                .iter()
7726                .any(|candidate| !same_logical_symbol(candidate, target));
7727        if !same_api {
7728            return false;
7729        }
7730
7731        let requirements = candidates
7732            .iter()
7733            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7734            .collect::<Vec<_>>();
7735        requirements.len() > 1
7736            && requirements
7737                .iter()
7738                .all(|requirement| !requirement.is_empty())
7739            && requirements.iter().enumerate().all(|(index, left)| {
7740                requirements[index + 1..].iter().all(|right| {
7741                    left.iter().all(|(_, left_guards)| {
7742                        right.iter().all(|(_, right_guards)| {
7743                            merge_preprocessor_guards(left_guards, right_guards).is_none()
7744                        })
7745                    })
7746                })
7747            })
7748    }
7749
7750    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7751        let mut pending = vec![terms.to_vec()];
7752        while let Some(branch_terms) = pending.pop() {
7753            let mut normalized = Vec::new();
7754            let mut covers_branch = false;
7755            for term in branch_terms {
7756                if term.iter().any(|guard| term.contains(&guard.negated())) {
7757                    continue;
7758                }
7759                if term.is_empty() {
7760                    covers_branch = true;
7761                    break;
7762                }
7763                if !normalized.iter().any(|existing| existing == &term) {
7764                    normalized.push(term);
7765                }
7766            }
7767            if covers_branch {
7768                continue;
7769            }
7770            let Some(split_guard) = normalized
7771                .iter()
7772                .flat_map(|term| term.iter())
7773                .next()
7774                .cloned()
7775            else {
7776                return false;
7777            };
7778            let negated_guard = split_guard.negated();
7779            let mut when_defined = Vec::new();
7780            let mut when_undefined = Vec::new();
7781            for term in normalized {
7782                if term.contains(&negated_guard) {
7783                    // This term cannot hold when `split_guard` is true.
7784                } else if term.contains(&split_guard) {
7785                    let mut reduced = term.clone();
7786                    reduced.remove(&split_guard);
7787                    when_defined.push(reduced);
7788                } else {
7789                    when_defined.push(term.clone());
7790                }
7791                if term.contains(&split_guard) {
7792                    // This term cannot hold when `split_guard` is false.
7793                } else if term.contains(&negated_guard) {
7794                    let mut reduced = term;
7795                    reduced.remove(&negated_guard);
7796                    when_undefined.push(reduced);
7797                } else {
7798                    when_undefined.push(term);
7799                }
7800            }
7801            pending.push(when_defined);
7802            pending.push(when_undefined);
7803        }
7804        true
7805    }
7806
7807    /// The byte range of the one `#if` family with a terminal `#else` that holds
7808    /// every physical declaration of every candidate, or `None` when they do not
7809    /// share one such family.
7810    ///
7811    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
7812    /// whose macros changed between declarations. Require every physical range to
7813    /// belong to one syntax-tree family with a terminal `#else` before the terms
7814    /// can prove branch coverage.
7815    fn declarations_share_exhaustive_conditional_family(
7816        &self,
7817        analyzer: &CppGraphSource<'_>,
7818        candidates: &[&CodeUnit],
7819    ) -> Option<(usize, usize)> {
7820        let mut family_range = None;
7821        for candidate in candidates {
7822            let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
7823            let root = prepared.tree().root_node();
7824            let mut candidate_family = None;
7825            for range in analyzer.ranges(candidate) {
7826                let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
7827                let family = preprocessor_conditional_family_for_declaration(node)?;
7828                let key = (family.start_byte(), family.end_byte());
7829                if candidate_family.is_some_and(|existing| existing != key) {
7830                    return None;
7831                }
7832                candidate_family = Some(key);
7833            }
7834            let candidate_family = candidate_family?;
7835            if family_range.is_some_and(|existing| existing != candidate_family) {
7836                return None;
7837            }
7838            family_range = Some(candidate_family);
7839        }
7840        family_range
7841    }
7842
7843    pub fn complementary_same_fqn_type_declarations(
7844        &self,
7845        analyzer: &CppGraphSource<'_>,
7846        candidates: &[&CodeUnit],
7847        target: &CodeUnit,
7848    ) -> bool {
7849        if candidates.len() < 2
7850            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7851            || self
7852                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7853                .is_none()
7854        {
7855            return false;
7856        }
7857        Self::preprocessor_guard_terms_cover_all_paths(
7858            &self.declaration_family_guard_terms(analyzer, candidates),
7859        )
7860    }
7861
7862    fn declaration_family_guard_terms(
7863        &self,
7864        analyzer: &CppGraphSource<'_>,
7865        candidates: &[&CodeUnit],
7866    ) -> Vec<HashSet<PreprocessorGuard>> {
7867        candidates
7868            .iter()
7869            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7870            .map(|(_, guards)| guards)
7871            .collect()
7872    }
7873
7874    /// A callable name declared on every branch of one completed `#if`/`#else`
7875    /// family is declared on every configuration path, so a reference below the
7876    /// whole family sees one of the branches whatever the preprocessor decides.
7877    /// Answer the family's end byte: only past `#endif` is every branch's
7878    /// declaration behind the reference.
7879    ///
7880    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
7881    /// and shares both of its primitives. It does not require two distinct
7882    /// `CodeUnit`s: branches that declare the same signature can collapse into
7883    /// one unit carrying one physical range per branch.
7884    ///
7885    /// The branches are alternate spellings of one declaration, never competing
7886    /// declarations, so only the first branch stands for the family. Reporting
7887    /// every branch as visible would turn a name the source declares exactly
7888    /// once into an ambiguity between build configurations.
7889    fn exhaustive_guard_family_activation(
7890        &self,
7891        analyzer: &CppGraphSource<'_>,
7892        prepared: &PreparedSyntaxTree,
7893        candidate: &CodeUnit,
7894        reference: &CallableReferenceContext<'_>,
7895    ) -> Option<usize> {
7896        // Branch coverage says nothing about scope: a block-local declaration
7897        // stays invisible however many branches declare it.
7898        if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
7899            return None;
7900        }
7901        let family = self
7902            .visible_identifier_candidates(candidate.source(), candidate.identifier())
7903            .filter(|peer| {
7904                peer.kind() == candidate.kind()
7905                    && peer.fq_name() == candidate.fq_name()
7906                    && peer.source() == candidate.source()
7907            })
7908            .collect::<Vec<_>>();
7909        let (_, family_end) =
7910            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7911        if !Self::preprocessor_guard_terms_cover_all_paths(
7912            &self.declaration_family_guard_terms(analyzer, &family),
7913        ) {
7914            return None;
7915        }
7916        // A reference whose own guards pick one branch already reaches that
7917        // branch through the ordinary same-guard path; the family must not
7918        // resurrect the branch the reference contradicts.
7919        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7920            .iter()
7921            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7922        {
7923            return None;
7924        }
7925        (first_declaration_byte(analyzer, candidate)?
7926            == family
7927                .iter()
7928                .filter_map(|peer| first_declaration_byte(analyzer, peer))
7929                .min()?)
7930        .then_some(family_end)
7931    }
7932
7933    fn type_candidate_preserving_target(
7934        &self,
7935        analyzer: &CppGraphSource<'_>,
7936        visible_from: &ProjectFile,
7937        candidate: &CodeUnit,
7938        target: &CodeUnit,
7939    ) -> Option<CodeUnit> {
7940        let mut current = candidate.clone();
7941        let mut matched_target = same_visible_symbol(&current, target)
7942            || self.compatible_primary_template_redeclarations(&current, target);
7943        let mut seen = HashSet::default();
7944        loop {
7945            if !seen.insert(current.clone()) {
7946                return None;
7947            }
7948            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
7949                return matched_target
7950                    .then(|| target.clone())
7951                    .or_else(|| current.is_class().then_some(current));
7952            };
7953            if self.flattened_macro_namespace_alias_target_matches(
7954                analyzer,
7955                visible_from,
7956                &current,
7957                &alias_target,
7958                target,
7959            ) {
7960                return Some(target.clone());
7961            }
7962            if matches!(alias_target, StructuredAliasTarget::Builtin) {
7963                return matched_target
7964                    .then(|| target.clone())
7965                    .or_else(|| current.is_class().then_some(current));
7966            }
7967            // A non-template alias can name a template alias with explicit
7968            // arguments (for example, `using Result = Expected<int>`).  When
7969            // the requested target is that alias's primary declaration, keep
7970            // the primary identity before expanding the RHS arguments.  The
7971            // expansion would otherwise canonicalize through the underlying
7972            // implementation type and lose the target spelling used by the
7973            // forward resolver.
7974            if !self.cpp_template_metadata.contains_key(&current)
7975                && let Some(primary) =
7976                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
7977                && (same_visible_symbol(&primary, target)
7978                    || self.compatible_primary_template_redeclarations(&primary, target))
7979            {
7980                return Some(target.clone());
7981            }
7982            if same_visible_symbol(&current, target) {
7983                return Some(target.clone());
7984            }
7985            if self.cpp_template_metadata.contains_key(&current) {
7986                return None;
7987            }
7988            let Some(next) =
7989                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
7990            else {
7991                return matched_target.then(|| target.clone());
7992            };
7993            current = next;
7994            matched_target |= same_visible_symbol(&current, target)
7995                || self.compatible_primary_template_redeclarations(&current, target);
7996        }
7997    }
7998
7999    fn compatible_primary_template_redeclarations(
8000        &self,
8001        left: &CodeUnit,
8002        right: &CodeUnit,
8003    ) -> bool {
8004        let (Some(left_metadata), Some(right_metadata)) = (
8005            self.cpp_template_metadata.get(left),
8006            self.cpp_template_metadata.get(right),
8007        ) else {
8008            return false;
8009        };
8010        left_metadata.primary_fq_name == right_metadata.primary_fq_name
8011            && left_metadata.is_primary()
8012            && right_metadata.is_primary()
8013            && cpp_reconcile_primary_template_parameters(
8014                &[(left, left_metadata), (right, right_metadata)],
8015                right,
8016            )
8017            .is_some()
8018    }
8019
8020    fn alias_candidate_may_preserve_target(
8021        &self,
8022        analyzer: &CppGraphSource<'_>,
8023        visible_from: &ProjectFile,
8024        candidate: &CodeUnit,
8025        target: &CodeUnit,
8026    ) -> bool {
8027        let mut current = candidate.clone();
8028        let mut seen = HashSet::default();
8029        loop {
8030            if same_visible_symbol(&current, target)
8031                || self.compatible_primary_template_redeclarations(&current, target)
8032            {
8033                return true;
8034            }
8035            if self.cpp_template_metadata.contains_key(&current) {
8036                return true;
8037            }
8038            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
8039                return false;
8040            };
8041            let StructuredAliasTarget::Named {
8042                components,
8043                global,
8044                arguments,
8045            } = alias_target
8046            else {
8047                return false;
8048            };
8049            if arguments.is_some() || !seen.insert(current.clone()) {
8050                return true;
8051            }
8052            let qualified = components.join("::");
8053            let next = if global {
8054                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8055            } else {
8056                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
8057            };
8058            let Some(next) = next else {
8059                return true;
8060            };
8061            current = next;
8062        }
8063    }
8064
8065    /// Every indexed type declaration `raw_name` names when it is written in
8066    /// `declaration`'s namespace: the innermost enclosing namespace that holds
8067    /// the name wins, otherwise the name is looked up unqualified.
8068    fn type_candidates_for_declaration<'b>(
8069        &'b self,
8070        visible_from: &ProjectFile,
8071        declaration: &CodeUnit,
8072        raw_name: &str,
8073    ) -> Vec<&'b CodeUnit> {
8074        let Some(normalized) = normalize_reference_name(raw_name) else {
8075            return Vec::new();
8076        };
8077        if let Some(namespace) = cpp_namespace_for(declaration) {
8078            for prefix in namespace_prefixes(&namespace) {
8079                let qualified = format!("{prefix}::{normalized}");
8080                let candidates = self.type_candidates(visible_from, &qualified);
8081                if !candidates.is_empty() {
8082                    return candidates;
8083                }
8084            }
8085        }
8086        self.type_candidates(visible_from, &normalized)
8087    }
8088
8089    fn resolve_unique_type_for_declaration(
8090        &self,
8091        visible_from: &ProjectFile,
8092        declaration: &CodeUnit,
8093        raw_name: &str,
8094    ) -> Option<CodeUnit> {
8095        unique_logical_type_candidate(self.type_candidates_for_declaration(
8096            visible_from,
8097            declaration,
8098            raw_name,
8099        ))
8100    }
8101
8102    /// The type `raw_name` names at `declaration`, answered with the
8103    /// declaration that defines it.
8104    ///
8105    /// C spells one type twice. `typedef struct T T;` in the header that
8106    /// publishes the handle indexes an incomplete `struct T` beside the
8107    /// complete `struct T { ... }` in the header that declares the members, and
8108    /// both carry the same kind and fully qualified name, so
8109    /// [`logical_type_candidate`] is free to answer with either. mbedtls writes
8110    /// exactly that: `include/mbedtls/ssl.h` forward-declares
8111    /// `mbedtls_ssl_handshake_params` for the pointer member
8112    /// `mbedtls_ssl_handshake_params *MBEDTLS_PRIVATE(handshake)` while
8113    /// `library/ssl_misc.h` defines its fields. A receiver typed from a field
8114    /// declaration is asked for its members on the next link of the chain, and
8115    /// only the definition owns them (#2982).
8116    ///
8117    /// Competing definitions leave nothing to choose between, so keep the
8118    /// logical answer rather than picking one of them.
8119    fn resolve_defining_type_for_declaration(
8120        &self,
8121        analyzer: &CppGraphSource<'_>,
8122        visible_from: &ProjectFile,
8123        declaration: &CodeUnit,
8124        raw_name: &str,
8125    ) -> Option<CodeUnit> {
8126        let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8127        let logical = unique_logical_type_candidate(candidates.clone())?;
8128        let mut defining = candidates.into_iter().filter(|candidate| {
8129            candidate.is_class()
8130                && cpp_class_declaration_strength(analyzer, candidate)
8131                    == CppClassDeclarationStrength::Full
8132        });
8133        match (defining.next(), defining.next()) {
8134            (Some(unique_definition), None) => Some(unique_definition.clone()),
8135            _ => Some(logical),
8136        }
8137    }
8138
8139    pub fn resolves_to_type(
8140        &self,
8141        analyzer: &CppGraphSource<'_>,
8142        file: &ProjectFile,
8143        raw_name: &str,
8144        target: &CodeUnit,
8145    ) -> bool {
8146        let Some(normalized) = normalize_reference_name(raw_name) else {
8147            return false;
8148        };
8149        let candidates = self.type_candidates(file, &normalized);
8150        if candidates.is_empty() {
8151            return self.parser_alias_resolves_to_type(file, raw_name, target);
8152        }
8153        let Some(resolved) =
8154            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8155        else {
8156            return false;
8157        };
8158        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8159    }
8160
8161    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8162        let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
8163        let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
8164        match resolved.kind() {
8165            CodeUnitType::Class => Some(resolved),
8166            _ if is_type_alias(&resolved) => self.alias_target(&resolved),
8167            _ => None,
8168        }
8169    }
8170
8171    /// Whether two callable declarations declare one function.
8172    ///
8173    /// [`same_logical_symbol`] compares the persisted signature strings, which
8174    /// embed each parameter type exactly as it was spelled. A header
8175    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
8176    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
8177    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
8178    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
8179    /// This resolves the written parameter names before comparing them and
8180    /// reports the same answer the language does for the cases it can prove.
8181    ///
8182    /// Everything it cannot prove stays two symbols: a template declaration, a
8183    /// parameter with no comparable shape, a name that resolves on one side
8184    /// only, and an alias chain it cannot follow safely (#2010).
8185    pub fn same_logical_callable(
8186        &self,
8187        analyzer: &CppGraphSource<'_>,
8188        left: &CodeUnit,
8189        right: &CodeUnit,
8190    ) -> bool {
8191        if same_logical_symbol(left, right) {
8192            return true;
8193        }
8194        if left.kind() != right.kind()
8195            || !left.is_callable()
8196            || !right.is_callable()
8197            || left.fq_name() != right.fq_name()
8198        {
8199            return false;
8200        }
8201        // A template declaration and its out-of-line body can also diverge
8202        // outside the parameter list - `template <class T>` against
8203        // `template <typename T>` - and the template head is part of the
8204        // persisted signature. Deciding template-head equivalence is a
8205        // separate question, so templates keep string identity.
8206        if self.callable_is_template_declaration(analyzer, left)
8207            || self.callable_is_template_declaration(analyzer, right)
8208        {
8209            return false;
8210        }
8211        let (Some(left_comparable), Some(right_comparable)) = (
8212            self.callable_comparable(analyzer, left),
8213            self.callable_comparable(analyzer, right),
8214        ) else {
8215            return false;
8216        };
8217        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
8218        // return type and requires-clause are part of C++ callable identity and
8219        // an out-of-line definition repeats them verbatim, so they must agree
8220        // as written.
8221        if left_comparable.suffix != right_comparable.suffix
8222            || left_comparable.shapes.len() != right_comparable.shapes.len()
8223        {
8224            return false;
8225        }
8226        left_comparable
8227            .shapes
8228            .iter()
8229            .zip(right_comparable.shapes.iter())
8230            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8231                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8232                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8233                    self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8234                }
8235                // An unstructured parameter records that the reduction failed,
8236                // not that the two spellings mean the same type, so it agrees
8237                // with nothing - including another unstructured parameter.
8238                _ => false,
8239            })
8240    }
8241
8242    /// Compare two parameter shapes node by node with an explicit paired stack.
8243    ///
8244    /// Shape variants and cv-qualifiers must agree exactly at every level; only
8245    /// the named leaves may be spelled differently, and they agree when they
8246    /// resolve to one type declaration.
8247    fn comparable_shapes_agree(
8248        &self,
8249        analyzer: &CppGraphSource<'_>,
8250        left: &CppComparableParameter,
8251        right: &CppComparableParameter,
8252    ) -> bool {
8253        let mut stack = vec![(left.root(), right.root())];
8254        while let Some((left_index, right_index)) = stack.pop() {
8255            match (left.node(left_index), right.node(right_index)) {
8256                (
8257                    CppComparableNode::Named {
8258                        name: left_name,
8259                        primitive: left_primitive,
8260                        konst: left_konst,
8261                        volatil: left_volatil,
8262                    },
8263                    CppComparableNode::Named {
8264                        name: right_name,
8265                        primitive: right_primitive,
8266                        konst: right_konst,
8267                        volatil: right_volatil,
8268                    },
8269                ) => {
8270                    if left_konst != right_konst
8271                        || left_volatil != right_volatil
8272                        || left_primitive != right_primitive
8273                        || !self.comparable_names_agree(
8274                            analyzer,
8275                            left_name,
8276                            right_name,
8277                            *left_primitive,
8278                        )
8279                    {
8280                        return false;
8281                    }
8282                }
8283                (
8284                    CppComparableNode::Pointer {
8285                        inner: left_inner,
8286                        konst: left_konst,
8287                        volatil: left_volatil,
8288                    },
8289                    CppComparableNode::Pointer {
8290                        inner: right_inner,
8291                        konst: right_konst,
8292                        volatil: right_volatil,
8293                    },
8294                ) => {
8295                    if left_konst != right_konst || left_volatil != right_volatil {
8296                        return false;
8297                    }
8298                    stack.push((*left_inner, *right_inner));
8299                }
8300                (
8301                    CppComparableNode::Reference { inner: left_inner },
8302                    CppComparableNode::Reference { inner: right_inner },
8303                )
8304                | (
8305                    CppComparableNode::Array { inner: left_inner },
8306                    CppComparableNode::Array { inner: right_inner },
8307                ) => stack.push((*left_inner, *right_inner)),
8308                (
8309                    CppComparableNode::Generic {
8310                        base: left_base,
8311                        arguments: left_arguments,
8312                    },
8313                    CppComparableNode::Generic {
8314                        base: right_base,
8315                        arguments: right_arguments,
8316                    },
8317                ) => {
8318                    if left_arguments.len() != right_arguments.len() {
8319                        return false;
8320                    }
8321                    stack.push((*left_base, *right_base));
8322                    stack.extend(
8323                        left_arguments.iter().zip(right_arguments.iter()).map(
8324                            |(left_argument, right_argument)| (*left_argument, *right_argument),
8325                        ),
8326                    );
8327                }
8328                _ => return false,
8329            }
8330        }
8331        true
8332    }
8333
8334    /// Whether two written type names denote one type.
8335    ///
8336    /// A primitive denotes the same type in every scope, so its recorded
8337    /// lexical scope is noise and its spelling decides. A nominal name is
8338    /// resolved on each side independently: two resolved names agree when they
8339    /// reach one type declaration, and two unresolved names agree only on
8340    /// exact agreement of what was written, which is no weaker than the
8341    /// whole-signature string equality this comparison replaces. Resolution on
8342    /// one side only is evidence of difference, never of agreement.
8343    fn comparable_names_agree(
8344        &self,
8345        analyzer: &CppGraphSource<'_>,
8346        left: &StructuredTypeName,
8347        right: &StructuredTypeName,
8348        primitive: bool,
8349    ) -> bool {
8350        if primitive {
8351            return left.path() == right.path();
8352        }
8353        match (
8354            self.comparable_name_terminal(analyzer, left),
8355            self.comparable_name_terminal(analyzer, right),
8356        ) {
8357            (Some(left_terminal), Some(right_terminal)) => {
8358                same_logical_symbol(&left_terminal, &right_terminal)
8359            }
8360            (None, None) => {
8361                left.path() == right.path() && left.is_absolute() == right.is_absolute()
8362            }
8363            _ => false,
8364        }
8365    }
8366
8367    /// The class declaration a written type name denotes, or `None` when the
8368    /// workspace cannot prove one.
8369    ///
8370    /// The lookup is a closure-independent lexical-scope prefix walk over the
8371    /// stored C++ identifier index rather than a visibility lookup: a body's
8372    /// `.cpp` is almost never in the reference file's include closure. Any name
8373    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
8374    /// cannot invent a type the compiler could not see; `using`-directives are
8375    /// not modelled, and a name that needs one stays unresolved.
8376    fn comparable_name_terminal(
8377        &self,
8378        analyzer: &CppGraphSource<'_>,
8379        name: &StructuredTypeName,
8380    ) -> Option<CodeUnit> {
8381        let mut current = self.comparable_name_declaration(analyzer, name)?;
8382        let mut visited = HashSet::default();
8383        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8384            // The alias question is asked before the class question, and
8385            // through `declared_type_alias` rather than `is_type_alias`,
8386            // because extraction records `using A8 = A7;` as a *Class* unit
8387            // whose signature is the alias declaration. Reading the kind first
8388            // would end the chase on the alias itself and report an alias
8389            // spelling and its underlying class as two types (#2010).
8390            if !declared_type_alias(analyzer, &current) {
8391                return current.is_class().then_some(current);
8392            }
8393            if !visited.insert(current.clone()) {
8394                return None;
8395            }
8396            let signature = current.signature()?;
8397            // `cpp_alias_declaration_target_text` reads the declaration's
8398            // `type` field only, so `typedef Foo *Bar` reports `Foo` and the
8399            // pointer is silently dropped. Substituting such an alias would
8400            // fuse `f(Bar)` and `f(Foo)`, which are two functions.
8401            if cpp_alias_declaration_adds_indirection(signature) {
8402                return None;
8403            }
8404            let raw_target = cpp_alias_declaration_target_text(signature)?;
8405            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
8406        }
8407        None
8408    }
8409
8410    /// The declaration one alias hop lands on: the type `raw_target` names,
8411    /// looked up from the alias declaration's own enclosing namespace.
8412    ///
8413    /// The hop takes the same closure-independent prefix walk the first lookup
8414    /// took, and deliberately not `resolve_type_for_declaration`: that one
8415    /// answers out of the `VisibilityIndex`, which is rooted at the reference
8416    /// file, while the alias declaration this hop starts from is reached
8417    /// through the workspace definition index and its file need not be in that
8418    /// root's include closure - where the visibility lookup answers nothing and
8419    /// the chase would stop on the alias itself (#2010).
8420    fn comparable_alias_target(
8421        &self,
8422        analyzer: &CppGraphSource<'_>,
8423        alias: &CodeUnit,
8424        raw_target: &str,
8425    ) -> Option<CodeUnit> {
8426        // `raw_target` is the alias declaration's written type text, so it is a
8427        // plain `::`-joined qualified-id: the same domain the shared symbol-path
8428        // parser reads, and the same leading `::` that marks an absolute name
8429        // everywhere else this crate normalizes a reference.
8430        let absolute = raw_target.trim_start().starts_with("::");
8431        let normalized = normalize_reference_name(raw_target)?;
8432        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8433            brokk_bifrost_core::analyzer::Language::Cpp,
8434            &normalized,
8435        );
8436        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8437            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8438                brokk_bifrost_core::analyzer::Language::Cpp,
8439                &namespace,
8440            )
8441        });
8442        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8443        self.comparable_name_declaration(analyzer, &name)
8444    }
8445
8446    /// The one type declaration `name` names, by enclosing scope, innermost
8447    /// first.
8448    ///
8449    /// The first prefix depth that names anything decides: an inner scope hides
8450    /// an outer one, so a match there is the answer even when an outer scope
8451    /// also declares the name. Several logically distinct declarations at that
8452    /// depth are an ambiguity this comparison must not guess at.
8453    fn comparable_name_declaration(
8454        &self,
8455        analyzer: &CppGraphSource<'_>,
8456        name: &StructuredTypeName,
8457    ) -> Option<CodeUnit> {
8458        // The same parameter-name pair is commonly compared once for every
8459        // physical declaration in a candidate set. Keep the index lookup under
8460        // the cache lock so concurrent workers cannot all miss the same key
8461        // and repeat the workspace candidate filter. `None` is a meaningful
8462        // result too: unresolved and ambiguous names must remain unresolved on
8463        // every later comparison.
8464        let mut cache = self
8465            .comparable_name_declarations
8466            .lock()
8467            .expect("C++ comparable name declaration cache poisoned");
8468        if let Some(cached) = cache.get(name) {
8469            return cached.clone();
8470        }
8471        let interner = segment_interner();
8472        let identifier = name.path().last()?;
8473        let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8474        let first_depth = if name.is_absolute() {
8475            0
8476        } else {
8477            name.lexical_scope().len()
8478        };
8479        let mut resolved = None;
8480        for depth in (0..=first_depth).rev() {
8481            let mut structured = FqName::new();
8482            for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8483                structured.push(interner.intern(component, SegmentKind::Unknown));
8484            }
8485            let mut candidates = candidates_by_identifier
8486                .iter()
8487                .filter(|unit| unit.fq().same_segment_texts(&structured))
8488                .filter(|unit| {
8489                    unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8490                })
8491                .cloned();
8492            let Some(first) = candidates.next() else {
8493                continue;
8494            };
8495            resolved = candidates
8496                .all(|unit| same_logical_symbol(&unit, &first))
8497                .then_some(first);
8498            break;
8499        }
8500        cache.insert(name.clone(), resolved.clone());
8501        resolved
8502    }
8503
8504    /// The comparison inputs of one callable declaration, extracted once.
8505    ///
8506    /// The comparison itself runs only when two candidates share kind and fully
8507    /// qualified name but not signature, which is rare; re-reading the same
8508    /// declaration for every pair in a candidate set is not.
8509    fn callable_comparable(
8510        &self,
8511        analyzer: &CppGraphSource<'_>,
8512        unit: &CodeUnit,
8513    ) -> Option<Arc<ExtractedComparable>> {
8514        if let Some(cached) = self
8515            .callable_comparables
8516            .lock()
8517            .expect("C++ callable comparable cache poisoned")
8518            .get(unit)
8519            .cloned()
8520        {
8521            return cached;
8522        }
8523        let extracted = self
8524            .extract_callable_comparable(analyzer, unit)
8525            .map(Arc::new);
8526        self.callable_comparables
8527            .lock()
8528            .expect("C++ callable comparable cache poisoned")
8529            .insert(unit.clone(), extracted.clone());
8530        extracted
8531    }
8532
8533    fn extract_callable_comparable(
8534        &self,
8535        analyzer: &CppGraphSource<'_>,
8536        unit: &CodeUnit,
8537    ) -> Option<ExtractedComparable> {
8538        let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
8539        let root = prepared.tree().root_node();
8540        let declarator = analyzer
8541            .ranges(unit)
8542            .into_iter()
8543            .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
8544        Some(ExtractedComparable {
8545            // One question about one declarator: indexing the file's tree would
8546            // cost more than the walk it saves.
8547            shapes: cpp_comparable_parameter_shapes(
8548                declarator,
8549                prepared.source(),
8550                &ParentIndex::unindexed(),
8551            ),
8552            suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
8553        })
8554    }
8555
8556    pub fn canonical_type_for_reference(
8557        &self,
8558        file: &ProjectFile,
8559        raw_name: &str,
8560    ) -> Option<CodeUnit> {
8561        let resolved = self.resolve_type(file, raw_name)?;
8562        self.alias_target(&resolved).or(Some(resolved))
8563    }
8564
8565    pub fn parser_alias_resolves_to_type(
8566        &self,
8567        file: &ProjectFile,
8568        raw_name: &str,
8569        target: &CodeUnit,
8570    ) -> bool {
8571        let Some(alias_name) = normalize_reference_name(raw_name) else {
8572            return false;
8573        };
8574        self.parser_alias_name_may_resolve_to_target(file, &alias_name, target)
8575    }
8576
8577    #[cfg(any(test, feature = "test-support"))]
8578    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8579        self.visible_source_files_by_root
8580            .get(file)
8581            .cloned()
8582            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8583    }
8584
8585    #[cfg(any(test, feature = "test-support"))]
8586    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8587        self.alias_source_parse_counts
8588            .lock()
8589            .expect("alias source parse count lock")
8590            .get(file)
8591            .copied()
8592            .unwrap_or(0)
8593    }
8594
8595    #[cfg(any(test, feature = "test-support"))]
8596    pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8597        self.parser_alias_fallback_files.load(Ordering::Relaxed)
8598    }
8599
8600    pub fn resolve_named(
8601        &self,
8602        file: &ProjectFile,
8603        raw_name: &str,
8604        kind: TargetKind,
8605    ) -> Option<CodeUnit> {
8606        let normalized = normalize_reference_name(raw_name)?;
8607        self.named_candidates_for_normalized(file, &normalized, kind)
8608            .into_iter()
8609            .next()
8610            .cloned()
8611    }
8612
8613    pub fn contains_named_symbol(
8614        &self,
8615        file: &ProjectFile,
8616        raw_name: &str,
8617        kind: TargetKind,
8618        target: &CodeUnit,
8619    ) -> bool {
8620        let Some(normalized) = normalize_reference_name(raw_name) else {
8621            return false;
8622        };
8623        self.named_candidates_for_normalized(file, &normalized, kind)
8624            .into_iter()
8625            .any(|unit| {
8626                matches_kind_for_lookup(unit, kind)
8627                    && reference_matches_unit(&normalized, unit)
8628                    && same_visible_symbol(unit, target)
8629            })
8630    }
8631
8632    pub fn named_candidates(
8633        &self,
8634        file: &ProjectFile,
8635        raw_name: &str,
8636        kind: TargetKind,
8637    ) -> Vec<CodeUnit> {
8638        let Some(normalized) = normalize_reference_name(raw_name) else {
8639            return Vec::new();
8640        };
8641        self.named_candidates_for_normalized(file, &normalized, kind)
8642            .into_iter()
8643            .cloned()
8644            .collect()
8645    }
8646
8647    pub fn resolve_known_non_target(
8648        &self,
8649        file: &ProjectFile,
8650        raw_name: &str,
8651        kind: TargetKind,
8652        target: &CodeUnit,
8653    ) -> bool {
8654        let Some(normalized) = normalize_reference_name(raw_name) else {
8655            return false;
8656        };
8657        normalized.contains("::")
8658            && self
8659                .named_candidates_for_normalized(file, &normalized, kind)
8660                .into_iter()
8661                .any(|unit| {
8662                    matches_kind_for_lookup(unit, kind)
8663                        && reference_matches_unit(&normalized, unit)
8664                        && !same_visible_symbol(unit, target)
8665                })
8666    }
8667
8668    pub fn resolve_call_return_binding(
8669        &self,
8670        analyzer: &CppGraphSource<'_>,
8671        file: &ProjectFile,
8672        raw_name: &str,
8673        arity: usize,
8674        lexical_namespace: Option<&str>,
8675        direct_type: Option<&CodeUnit>,
8676    ) -> Option<CppScanBinding> {
8677        let normalized = normalize_reference_name(raw_name)?;
8678        let mut candidates = Vec::new();
8679        for function in
8680            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8681        {
8682            if cpp_callable_arity(analyzer, function).accepts(arity)
8683                && !direct_type.is_some_and(|direct_type| {
8684                    self.callable_is_constructor_declaration(analyzer, function)
8685                        && type_owner_of(analyzer, function)
8686                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8687                })
8688            {
8689                candidates.push(function.clone());
8690            }
8691        }
8692        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8693        unanimous_return_binding(analyzer, self, file, &candidates)
8694    }
8695
8696    pub fn resolve_call_return_binding_without_arity(
8697        &self,
8698        analyzer: &CppGraphSource<'_>,
8699        file: &ProjectFile,
8700        raw_name: &str,
8701        lexical_namespace: Option<&str>,
8702        direct_type: Option<&CodeUnit>,
8703    ) -> (bool, Option<CppScanBinding>) {
8704        let Some(normalized) = normalize_reference_name(raw_name) else {
8705            return (false, None);
8706        };
8707        let mut candidates = self
8708            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8709            .into_iter()
8710            .filter(|function| {
8711                function.is_function()
8712                    && !direct_type.is_some_and(|direct_type| {
8713                        self.callable_is_constructor_declaration(analyzer, function)
8714                            && type_owner_of(analyzer, function)
8715                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8716                    })
8717            })
8718            .cloned()
8719            .collect::<Vec<_>>();
8720        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8721        let has_candidates = !candidates.is_empty();
8722        (
8723            has_candidates,
8724            unanimous_return_binding(analyzer, self, file, &candidates),
8725        )
8726    }
8727
8728    pub fn visible_identifier_candidates<'b>(
8729        &'b self,
8730        file: &ProjectFile,
8731        identifier: &str,
8732    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8733        self.visible_by_identifier
8734            .get(file)
8735            .and_then(|by_name| by_name.get(identifier))
8736            .into_iter()
8737            .flatten()
8738    }
8739
8740    /// Return terminal reference names that can denote `target` from `file`.
8741    ///
8742    /// The indexed candidate table covers ordinary declarations and aliases;
8743    /// Parser-only aliases are tested lazily when their spelling is actually
8744    /// encountered in a scanned type node. Enumerating them here would parse
8745    /// every source in the include closure even when the target's direct name
8746    /// is the only spelling present in the file.
8747    pub fn visible_type_reference_component_names_for_target(
8748        &self,
8749        analyzer: &CppGraphSource<'_>,
8750        file: &ProjectFile,
8751        target: &CodeUnit,
8752    ) -> HashSet<String> {
8753        let mut names = HashSet::from_iter([target.identifier().to_string()]);
8754        if let Some(metadata) = self.cpp_template_metadata.get(target) {
8755            names.insert(metadata.primary_name.clone());
8756        }
8757
8758        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8759            for (identifier, candidates) in by_identifier {
8760                if candidates.iter().any(|candidate| {
8761                    (candidate.is_class()
8762                        && (same_visible_symbol(candidate, target)
8763                            || self.compatible_primary_template_redeclarations(candidate, target)))
8764                        || (declared_type_alias(analyzer, candidate)
8765                            && self.alias_candidate_may_preserve_target(
8766                                analyzer, file, candidate, target,
8767                            ))
8768                }) {
8769                    names.insert(identifier.clone());
8770                }
8771            }
8772        }
8773
8774        names
8775    }
8776
8777    pub fn indexed_structural_class_scope(
8778        &self,
8779        file: &ProjectFile,
8780        class: Node<'_>,
8781        source: &str,
8782    ) -> Option<Vec<String>> {
8783        let key = (file.clone(), class.start_byte(), class.end_byte());
8784        if let Some(cached) = self
8785            .indexed_structural_class_scopes
8786            .lock()
8787            .expect("C++ indexed structural-class scope cache poisoned")
8788            .get(&key)
8789            .cloned()
8790        {
8791            return cached;
8792        }
8793        let resolved = (|| {
8794            let name = class.child_by_field_name("name")?;
8795            let identifier = if name.kind() == "template_type" {
8796                node_text(name.child_by_field_name("name")?, source).to_string()
8797            } else {
8798                let mut components = Vec::new();
8799                append_cpp_name_components(name, source, &mut components)?;
8800                components.last()?.clone()
8801            };
8802            let visible = self
8803                .visible_identifier_candidates(file, &identifier)
8804                .cloned()
8805                .collect::<Vec<_>>();
8806            let mut visible = visible;
8807            for candidate in
8808                self.visible_by_file
8809                    .get(file)
8810                    .into_iter()
8811                    .flatten()
8812                    .filter(|candidate| {
8813                        self.cpp_template_metadata
8814                            .get(candidate)
8815                            .is_some_and(|metadata| metadata.primary_name == identifier)
8816                    })
8817            {
8818                if !visible
8819                    .iter()
8820                    .any(|existing| same_logical_symbol(existing, candidate))
8821                {
8822                    visible.push(candidate.clone());
8823                }
8824            }
8825            // Built once per call rather than per candidate; `cpp_source` rebuilds
8826            // the five-field source from the same `self.cpp` on every call.
8827            let cpp_source = self.cpp_source();
8828            let candidates = visible
8829                .iter()
8830                .filter(|candidate| {
8831                    candidate.source() == file
8832                        && candidate.is_class()
8833                        && !declared_type_alias(&cpp_source, candidate)
8834                        && self.cpp.ranges(candidate).iter().any(|range| {
8835                            range.start_byte <= class.start_byte()
8836                                && class.end_byte() <= range.end_byte
8837                        })
8838                })
8839                .collect::<Vec<_>>();
8840            let owner = if name.kind() == "template_type" {
8841                let expected = normalize_cpp_whitespace(node_text(name, source));
8842                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
8843                let exact = candidates
8844                    .iter()
8845                    .copied()
8846                    .filter(|candidate| {
8847                        candidate
8848                            .fq()
8849                            .segments()
8850                            .iter()
8851                            .rev()
8852                            .find_map(|&segment| {
8853                                let (text, kind) = interner.resolve(segment);
8854                                matches!(
8855                                    kind,
8856                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
8857                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
8858                                )
8859                                .then_some(text)
8860                            })
8861                            .is_some_and(|text| text == expected)
8862                    })
8863                    .collect::<Vec<_>>();
8864                unique_logical_type_candidate(exact)
8865                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
8866            } else {
8867                unique_logical_type_candidate(candidates)?
8868            };
8869            Some(canonical_cpp_scope_components(&owner))
8870        })();
8871        self.indexed_structural_class_scopes
8872            .lock()
8873            .expect("C++ indexed structural-class scope cache poisoned")
8874            .insert(key, resolved.clone());
8875        resolved
8876    }
8877
8878    pub fn indexed_enclosing_owner_scope(
8879        &self,
8880        analyzer: &CppGraphSource<'_>,
8881        file: &ProjectFile,
8882        node: Node<'_>,
8883    ) -> Option<Vec<String>> {
8884        let anchor = std::iter::successors(Some(node), |current| current.parent())
8885            .find(|current| {
8886                matches!(
8887                    current.kind(),
8888                    "function_definition"
8889                        | "class_specifier"
8890                        | "struct_specifier"
8891                        | "union_specifier"
8892                )
8893            })
8894            .unwrap_or(node);
8895        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
8896        if let Some(cached) = self
8897            .indexed_enclosing_owner_scopes
8898            .lock()
8899            .expect("C++ indexed enclosing-owner scope cache poisoned")
8900            .get(&key)
8901            .cloned()
8902        {
8903            return cached;
8904        }
8905        let resolved = (|| {
8906            let range = Range {
8907                start_byte: node.start_byte(),
8908                end_byte: node.end_byte(),
8909                start_line: node.start_position().row,
8910                end_line: node.end_position().row,
8911            };
8912            let start = analyzer.enclosing_code_unit(file, &range)?;
8913            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
8914                start,
8915                |unit| self.cached_precise_parent_of(analyzer, unit),
8916            )
8917            .find(|unit| {
8918                unit.is_class()
8919                    && !analyzer
8920                        .type_alias_provider()
8921                        .is_some_and(|provider| provider.is_type_alias(unit))
8922            })?;
8923            Some(canonical_cpp_scope_components(&owner))
8924        })();
8925        self.indexed_enclosing_owner_scopes
8926            .lock()
8927            .expect("C++ indexed enclosing-owner scope cache poisoned")
8928            .insert(key, resolved.clone());
8929        resolved
8930    }
8931
8932    fn cached_precise_parent_of(
8933        &self,
8934        analyzer: &CppGraphSource<'_>,
8935        code_unit: &CodeUnit,
8936    ) -> Option<CodeUnit> {
8937        if let Some(cached) = self
8938            .precise_parent_cache
8939            .lock()
8940            .expect("C++ precise-parent cache poisoned")
8941            .get(code_unit)
8942            .cloned()
8943        {
8944            return cached;
8945        }
8946        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
8947        self.precise_parent_cache
8948            .lock()
8949            .expect("C++ precise-parent cache poisoned")
8950            .insert(code_unit.clone(), resolved.clone());
8951        resolved
8952    }
8953
8954    pub fn callable_is_constructor_declaration(
8955        &self,
8956        analyzer: &CppGraphSource<'_>,
8957        candidate: &CodeUnit,
8958    ) -> bool {
8959        if !candidate.is_function() {
8960            return false;
8961        }
8962        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
8963            return false;
8964        };
8965        let root = prepared.tree().root_node();
8966        let candidate_ranges = analyzer.ranges(candidate);
8967        let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
8968            let mut current = root
8969                .descendant_for_byte_range(range.start_byte, range.end_byte)
8970                .and_then(|node| node.parent());
8971            while let Some(node) = current {
8972                if matches!(
8973                    node.kind(),
8974                    "class_specifier" | "struct_specifier" | "union_specifier"
8975                ) {
8976                    return node
8977                        .child_by_field_name("name")
8978                        .map(|name| terminal_name(node_text(name, prepared.source())))
8979                        .is_some_and(|name| name == candidate.identifier());
8980                }
8981                current = node.parent();
8982            }
8983            false
8984        });
8985        if enclosed_by_matching_type {
8986            return true;
8987        }
8988        let indexed_containment = analyzer
8989            .declarations(candidate.source())
8990            .into_iter()
8991            .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
8992            .any(|owner| {
8993                analyzer.ranges(&owner).iter().any(|owner_range| {
8994                    candidate_ranges.iter().any(|candidate_range| {
8995                        owner_range.start_byte <= candidate_range.start_byte
8996                            && candidate_range.end_byte <= owner_range.end_byte
8997                    })
8998                })
8999            });
9000        if indexed_containment {
9001            return true;
9002        }
9003        let metadata = analyzer.signature_metadata(candidate);
9004        !metadata.is_empty()
9005            && metadata
9006                .iter()
9007                .all(|signature| signature.return_type_text().is_none())
9008    }
9009
9010    /// Whether a callable declaration is a class-template deduction guide.
9011    ///
9012    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
9013    /// type field whose function declarator owns a trailing return type. This
9014    /// structured shape distinguishes a guide from both a constructor (no
9015    /// trailing return) and an ordinary trailing-return function (an `auto`
9016    /// type field).
9017    pub fn callable_is_deduction_guide_declaration(
9018        &self,
9019        analyzer: &CppGraphSource<'_>,
9020        candidate: &CodeUnit,
9021    ) -> bool {
9022        if !candidate.is_function() {
9023            return false;
9024        }
9025        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9026            return false;
9027        };
9028        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
9029            .into_iter()
9030            .any(|declaration| {
9031                if declaration.kind() != "declaration"
9032                    || declaration.child_by_field_name("type").is_some()
9033                {
9034                    return false;
9035                }
9036                let Some(declarator) = declaration.child_by_field_name("declarator") else {
9037                    return false;
9038                };
9039                if declarator.kind() != "function_declarator" {
9040                    return false;
9041                }
9042                let mut cursor = declarator.walk();
9043                let has_trailing_return = declarator
9044                    .named_children(&mut cursor)
9045                    .any(|child| child.kind() == "trailing_return_type");
9046                has_trailing_return
9047                    && declarator_name_node(declarator).is_some_and(|name| {
9048                        node_text(name, prepared.source()) == candidate.identifier()
9049                    })
9050            })
9051    }
9052
9053    /// Whether a callable occurrence is directly wrapped by a C++ template
9054    /// declaration. This deliberately inspects declaration syntax instead of
9055    /// inferring template status from the rendered signature.
9056    pub fn callable_is_template_declaration(
9057        &self,
9058        analyzer: &CppGraphSource<'_>,
9059        candidate: &CodeUnit,
9060    ) -> bool {
9061        if !candidate.is_function() {
9062            return false;
9063        }
9064        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
9065            return false;
9066        };
9067        let root = prepared.tree().root_node();
9068        analyzer.ranges(candidate).iter().any(|range| {
9069            let Some(node) = node_for_exact_range(root, range)
9070                .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
9071            else {
9072                return false;
9073            };
9074            node.parent().is_some_and(|parent| {
9075                parent.kind() == "template_declaration"
9076                    && parent
9077                        .named_child(parent.named_child_count().saturating_sub(1))
9078                        .is_some_and(|declaration| same_node(declaration, node))
9079            })
9080        })
9081    }
9082
9083    pub fn type_name_candidates<'b>(
9084        &'b self,
9085        file: &ProjectFile,
9086        normalized: &str,
9087    ) -> Vec<&'b CodeUnit> {
9088        self.candidate_units(file, normalized, TargetKind::Type)
9089    }
9090
9091    pub fn visible_members_for_owner_name<'b>(
9092        &'b self,
9093        file: &ProjectFile,
9094        owner: &CodeUnit,
9095        name: &str,
9096    ) -> Vec<&'b CodeUnit> {
9097        self.visible_identifier_candidates(file, name)
9098            .filter(|unit| {
9099                // Structured owner pop on the unit's own `fq()` (shared with
9100                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
9101                // string.
9102                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9103                    .is_some_and(|parent| parent == owner.fq_name())
9104            })
9105            .collect()
9106    }
9107
9108    pub fn visible_member_for_owner_name(
9109        &self,
9110        file: &ProjectFile,
9111        owner: &CodeUnit,
9112        name: &str,
9113    ) -> VisibleMemberResolution {
9114        let candidates = self.visible_members_for_owner_name(file, owner, name);
9115        let mut callables = Vec::new();
9116        let mut non_callable = None;
9117        for candidate in candidates {
9118            if candidate.is_function() {
9119                callables.push(candidate.clone());
9120            } else if non_callable.is_none() {
9121                non_callable = Some(candidate.clone());
9122            }
9123        }
9124        match (callables.is_empty(), non_callable) {
9125            (false, None) => VisibleMemberResolution::Callable(callables),
9126            (true, Some(_)) => VisibleMemberResolution::NonCallable,
9127            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9128            (true, None) => VisibleMemberResolution::Missing,
9129        }
9130    }
9131
9132    fn field_declared_type_fact(
9133        &self,
9134        analyzer: &CppGraphSource<'_>,
9135        field: &CodeUnit,
9136    ) -> Option<DeclaredFieldTypeFact> {
9137        if let Some(cached) = self
9138            .field_type_facts
9139            .lock()
9140            .expect("C++ field type fact cache poisoned")
9141            .get(field)
9142            .cloned()
9143        {
9144            return cached;
9145        }
9146        let decoded = decode_field_declared_type_fact(analyzer, field);
9147        self.field_type_facts
9148            .lock()
9149            .expect("C++ field type fact cache poisoned")
9150            .insert(field.clone(), decoded.clone());
9151        decoded
9152    }
9153
9154    fn structured_alias_target(
9155        &self,
9156        analyzer: &CppGraphSource<'_>,
9157        unit: &CodeUnit,
9158    ) -> Option<StructuredAliasTarget> {
9159        if let Some(cached) = self
9160            .structured_alias_targets
9161            .lock()
9162            .expect("C++ structured alias target cache poisoned")
9163            .get(unit)
9164            .cloned()
9165        {
9166            return cached;
9167        }
9168        let decoded = decode_structured_alias_target(analyzer, unit);
9169        self.structured_alias_targets
9170            .lock()
9171            .expect("C++ structured alias target cache poisoned")
9172            .insert(unit.clone(), decoded.clone());
9173        decoded
9174    }
9175
9176    pub fn type_candidates<'b>(
9177        &'b self,
9178        file: &ProjectFile,
9179        normalized: &str,
9180    ) -> Vec<&'b CodeUnit> {
9181        let mut candidates = self
9182            .candidate_units(file, normalized, TargetKind::Type)
9183            .into_iter()
9184            .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
9185            .collect::<Vec<_>>();
9186        dedup_unit_refs(&mut candidates);
9187        candidates
9188    }
9189
9190    pub fn named_candidates_for_normalized<'b>(
9191        &'b self,
9192        file: &ProjectFile,
9193        normalized: &str,
9194        kind: TargetKind,
9195    ) -> Vec<&'b CodeUnit> {
9196        let mut candidates = self
9197            .candidate_units(file, normalized, kind)
9198            .into_iter()
9199            .filter(|unit| {
9200                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9201            })
9202            .collect::<Vec<_>>();
9203        dedup_unit_refs(&mut candidates);
9204        candidates
9205    }
9206
9207    pub fn candidate_units<'b>(
9208        &'b self,
9209        file: &ProjectFile,
9210        normalized: &str,
9211        kind: TargetKind,
9212    ) -> Vec<&'b CodeUnit> {
9213        if normalized.contains("::") {
9214            // `normalized` comes from `normalize_cpp_reference_text`, which
9215            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
9216            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
9217            // kept intact by the shared splitter's operator merge — the same
9218            // domain `cpp_reference_fqn_candidates` below already parses with
9219            // the shared splitter. Re-tokenizing and taking the last segment
9220            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
9221            // scan exactly.
9222            let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9223                brokk_bifrost_core::analyzer::Language::Cpp,
9224                normalized,
9225            )
9226            .pop() else {
9227                return Vec::new();
9228            };
9229            let fqns = cpp_reference_fqn_candidates(normalized, kind);
9230            return self
9231                .visible_identifier_candidates(file, &identifier)
9232                .filter(|unit| {
9233                    #[cfg(any(test, feature = "test-support"))]
9234                    self.qualified_candidate_inspections
9235                        .fetch_add(1, Ordering::Relaxed);
9236                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9237                        || canonical_cpp_name_matches(unit, normalized)
9238                })
9239                .collect();
9240        }
9241        self.visible_identifier_candidates(file, normalized)
9242            .collect()
9243    }
9244
9245    #[cfg(any(test, feature = "test-support"))]
9246    pub fn reset_qualified_candidate_inspections(&self) {
9247        self.qualified_candidate_inspections
9248            .store(0, Ordering::Relaxed);
9249    }
9250
9251    #[cfg(any(test, feature = "test-support"))]
9252    pub fn qualified_candidate_inspections(&self) -> usize {
9253        self.qualified_candidate_inspections.load(Ordering::Relaxed)
9254    }
9255
9256    #[cfg(any(test, feature = "test-support"))]
9257    pub fn visibility_identifier_lookup_count(&self) -> usize {
9258        self.visibility_identifier_lookup_count
9259    }
9260
9261    #[cfg(any(test, feature = "test-support"))]
9262    pub fn visibility_identifier_batch_count(&self) -> usize {
9263        self.visibility_identifier_batch_count
9264    }
9265
9266    #[cfg(any(test, feature = "test-support"))]
9267    pub fn reset_target_preserving_type_resolution_count(&self) {
9268        self.target_preserving_type_resolution_count
9269            .store(0, Ordering::Relaxed);
9270    }
9271
9272    #[cfg(any(test, feature = "test-support"))]
9273    pub fn target_preserving_type_resolution_count(&self) -> usize {
9274        self.target_preserving_type_resolution_count
9275            .load(Ordering::Relaxed)
9276    }
9277
9278    #[cfg(any(test, feature = "test-support"))]
9279    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9280        self.visible_parser_alias_name_set_build_count
9281            .load(Ordering::Relaxed)
9282    }
9283}
9284
9285#[derive(Default)]
9286struct IncludeGraph {
9287    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9288}
9289
9290impl IncludeGraph {
9291    fn extend_with<F>(
9292        &mut self,
9293        root: &ProjectFile,
9294        cancellation: Option<&CancellationToken>,
9295        targets_for: &mut F,
9296    ) where
9297        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9298    {
9299        let mut stack = vec![root.clone()];
9300        while let Some(file) = stack.pop() {
9301            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9302                break;
9303            }
9304            if self.targets_by_file.contains_key(&file) {
9305                continue;
9306            }
9307            let targets = targets_for(&file);
9308            stack.extend(targets.iter().cloned());
9309            self.targets_by_file.insert(file, targets);
9310        }
9311    }
9312
9313    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9314        self.targets_by_file.keys()
9315    }
9316
9317    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9318        self.targets_by_file
9319            .get(file)
9320            .map(Vec::as_slice)
9321            .unwrap_or_default()
9322    }
9323
9324    fn reachable_files(
9325        &self,
9326        root: &ProjectFile,
9327        cancellation: Option<&CancellationToken>,
9328    ) -> HashSet<ProjectFile> {
9329        let mut pending = vec![root.clone()];
9330        let mut visited = HashSet::default();
9331        while let Some(file) = pending.pop() {
9332            if cancellation.is_some_and(CancellationToken::is_cancelled) {
9333                break;
9334            }
9335            if visited.insert(file.clone()) {
9336                pending.extend(self.targets(&file).iter().cloned());
9337            }
9338        }
9339        visited
9340    }
9341}
9342
9343fn build_bounded_visible_declarations(
9344    cpp: &dyn CppSource,
9345    token: QueryToken<'_>,
9346    analyzer: &CppGraphSource<'_>,
9347    roots: &HashSet<ProjectFile>,
9348    visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9349    cancellation: Option<&CancellationToken>,
9350    stats: &mut BoundedVisibilityStats,
9351) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9352    let mut candidates_by_identifier = HashMap::default();
9353    roots
9354        .iter()
9355        .map(|root| {
9356            let reading_is_c = analyzer.reference_uses_c_semantics(root);
9357            let declarations_started = Instant::now();
9358            let root_declarations =
9359                bounded_visibility_declarations_in_reading(analyzer, root, reading_is_c);
9360            stats.declaration_elapsed += declarations_started.elapsed();
9361            stats.declaration_reads += 1;
9362            stats.declaration_units += root_declarations.len();
9363            let mut visible = root_declarations.into_iter().collect::<HashSet<_>>();
9364            let mut pending_names = HashSet::default();
9365            if let Some(prepared) = cpp.prepared_syntax(token, root) {
9366                // One cursor for the whole file walk: `named_child(index)`
9367                // re-steps the sibling list from the first child on every
9368                // access, which made this whole-file scan quadratic in the
9369                // fan-out of each node (#3097).
9370                let mut cursor = prepared.tree().walk();
9371                let mut pending_nodes = vec![prepared.tree().root_node()];
9372                while let Some(node) = pending_nodes.pop() {
9373                    if matches!(
9374                        node.kind(),
9375                        "identifier"
9376                            | "type_identifier"
9377                            | "field_identifier"
9378                            | "namespace_identifier"
9379                    ) {
9380                        pending_names.insert(node_text(node, prepared.source()).to_string());
9381                    }
9382                    if node.kind() == "preproc_arg" {
9383                        for reference in
9384                            object_macro_replacement_type_references(node, prepared.source())
9385                        {
9386                            pending_names.extend(reference.components);
9387                        }
9388                    }
9389                    pending_nodes.extend(node.named_children(&mut cursor));
9390                }
9391            }
9392            stats.root_names += pending_names.len();
9393            let mut completed_names = HashSet::default();
9394            while !pending_names.is_empty() {
9395                stats.rounds += 1;
9396                let round_names = std::mem::take(&mut pending_names);
9397                let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9398                    HashMap::default();
9399                let mut identifiers = Vec::new();
9400                for identifier in round_names {
9401                    if !completed_names.insert(identifier.clone())
9402                        || cancellation.is_some_and(CancellationToken::is_cancelled)
9403                    {
9404                        continue;
9405                    }
9406                    identifiers.push(identifier);
9407                }
9408                let missing_identifiers = identifiers
9409                    .iter()
9410                    .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9411                    .cloned()
9412                    .collect::<HashSet<_>>();
9413                if !missing_identifiers.is_empty() {
9414                    let lookup_started = Instant::now();
9415                    let mut candidates = cpp
9416                        .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9417                    stats.lookup_elapsed += lookup_started.elapsed();
9418                    stats.identifier_lookups += missing_identifiers.len();
9419                    stats.identifier_batches += 1;
9420                    if cancellation.is_some_and(CancellationToken::is_cancelled) {
9421                        break;
9422                    }
9423                    for identifier in missing_identifiers {
9424                        let units = candidates.remove(&identifier).unwrap_or_default();
9425                        let candidate_count = units.len();
9426                        let candidate_sources = units
9427                            .into_iter()
9428                            .map(|unit| unit.source().clone())
9429                            .collect::<HashSet<_>>();
9430                        candidates_by_identifier
9431                            .insert(identifier, (candidate_sources, candidate_count));
9432                    }
9433                }
9434                for identifier in identifiers {
9435                    let (candidate_sources, candidate_count) = candidates_by_identifier
9436                        .get(&identifier)
9437                        .expect("every missing identifier was inserted after the batch lookup");
9438                    stats.candidate_units += *candidate_count;
9439                    for source in candidate_sources.iter().cloned() {
9440                        if source != *root
9441                            && visible_sources
9442                                .get(root)
9443                                .is_some_and(|files| files.contains(&source))
9444                        {
9445                            requested_names_by_source
9446                                .entry(source)
9447                                .or_default()
9448                                .insert(identifier.clone());
9449                        }
9450                    }
9451                }
9452                stats.candidate_sources += requested_names_by_source.len();
9453                for (source, requested_names) in requested_names_by_source {
9454                    let declarations_started = Instant::now();
9455                    let declarations =
9456                        bounded_visibility_declarations_in_reading(analyzer, &source, reading_is_c);
9457                    stats.declaration_elapsed += declarations_started.elapsed();
9458                    stats.declaration_reads += 1;
9459                    stats.declaration_units += declarations.len();
9460                    for unit in declarations {
9461                        let template_metadata = unit
9462                            .is_class()
9463                            .then(|| cpp.template_metadata(&unit))
9464                            .flatten();
9465                        if !requested_names.contains(unit.identifier())
9466                            && !template_metadata.as_ref().is_some_and(|metadata| {
9467                                requested_names.contains(&metadata.primary_name)
9468                            })
9469                        {
9470                            continue;
9471                        }
9472                        stats.selected_units += 1;
9473                        if let Some(prepared) = cpp.prepared_syntax(token, &source) {
9474                            let ast_started = Instant::now();
9475                            let mut cursor = prepared.tree().walk();
9476                            for range in analyzer.ranges(&unit) {
9477                                let Some(declaration) =
9478                                    node_for_exact_range(prepared.tree().root_node(), &range)
9479                                else {
9480                                    continue;
9481                                };
9482                                let mut pending_nodes = vec![declaration];
9483                                while let Some(node) = pending_nodes.pop() {
9484                                    stats.dependency_ast_nodes += 1;
9485                                    if matches!(
9486                                        node.kind(),
9487                                        "type_identifier" | "namespace_identifier"
9488                                    ) {
9489                                        let name = node_text(node, prepared.source());
9490                                        if !completed_names.contains(name)
9491                                            && pending_names.insert(name.to_string())
9492                                        {
9493                                            stats.dependency_names += 1;
9494                                        }
9495                                    }
9496                                    pending_nodes.extend(node.named_children(&mut cursor));
9497                                }
9498                            }
9499                            stats.dependency_ast_elapsed += ast_started.elapsed();
9500                        }
9501                        if let Some(metadata) = template_metadata
9502                            && !completed_names.contains(&metadata.primary_name)
9503                        {
9504                            pending_names.insert(metadata.primary_name);
9505                        }
9506                        visible.insert(unit);
9507                    }
9508                }
9509            }
9510            (root.clone(), visible)
9511        })
9512        .collect()
9513}
9514
9515#[derive(Default)]
9516struct BoundedVisibilityStats {
9517    rounds: usize,
9518    root_names: usize,
9519    identifier_lookups: usize,
9520    identifier_batches: usize,
9521    candidate_units: usize,
9522    candidate_sources: usize,
9523    declaration_reads: usize,
9524    declaration_units: usize,
9525    selected_units: usize,
9526    dependency_ast_nodes: usize,
9527    dependency_names: usize,
9528    lookup_elapsed: Duration,
9529    declaration_elapsed: Duration,
9530    dependency_ast_elapsed: Duration,
9531}
9532
9533fn bounded_visibility_declarations_in_reading(
9534    analyzer: &CppGraphSource<'_>,
9535    file: &ProjectFile,
9536    c_semantics: bool,
9537) -> BTreeSet<CodeUnit> {
9538    #[cfg(any(test, feature = "test-support"))]
9539    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9540    analyzer.declarations_in_reading(file, c_semantics)
9541}
9542
9543#[cfg(any(test, feature = "test-support"))]
9544pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9545    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9546}
9547
9548#[cfg(any(test, feature = "test-support"))]
9549pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9550    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9551}
9552
9553pub struct VisibilityData {
9554    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9555    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9556}
9557
9558/// Build the per-root include closure and the declarations each root can see
9559/// through it.
9560///
9561/// `declarations_for` takes the reading to answer in (issue #1970): a root
9562/// compiled as C sees the C reading of every file in its closure, a root
9563/// compiled as C++ sees the C++ reading, and `reading_is_c_for` decides which
9564/// per root. The two readings agree for all but a handful of headers, so the
9565/// C map is built only when some root actually asks for it, and only over the
9566/// files that root reaches.
9567pub fn build_visibility_data<F, R, D>(
9568    roots: &HashSet<ProjectFile>,
9569    cancellation: Option<&CancellationToken>,
9570    mut targets_for: F,
9571    mut reading_is_c_for: R,
9572    mut declarations_for: D,
9573) -> VisibilityData
9574where
9575    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9576    R: FnMut(&ProjectFile) -> bool,
9577    D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9578{
9579    let mut include_graph = IncludeGraph::default();
9580    for file in roots {
9581        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9582            break;
9583        }
9584        include_graph.extend_with(file, cancellation, &mut targets_for);
9585    }
9586    let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9587        .files()
9588        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9589        .map(|file| (file.clone(), declarations_for(file, false)))
9590        .collect();
9591    let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9592    let mut visible_by_file = HashMap::default();
9593    let mut visible_source_files_by_root = HashMap::default();
9594    for file in roots {
9595        if cancellation.is_some_and(CancellationToken::is_cancelled) {
9596            break;
9597        }
9598        let mut visited = HashSet::default();
9599        let mut visible = HashSet::default();
9600        let declarations_by_file = if reading_is_c_for(file) {
9601            for reached in cpp_declarations_by_file.keys() {
9602                if !c_declarations_by_file.contains_key(reached) {
9603                    let declarations = declarations_for(reached, true);
9604                    c_declarations_by_file.insert(reached.clone(), declarations);
9605                }
9606            }
9607            &c_declarations_by_file
9608        } else {
9609            &cpp_declarations_by_file
9610        };
9611        collect_visible_declarations(
9612            &include_graph,
9613            declarations_by_file,
9614            file,
9615            &mut visited,
9616            &mut visible,
9617            cancellation,
9618        );
9619        visible_by_file.insert(file.clone(), visible);
9620        visible_source_files_by_root.insert(file.clone(), visited);
9621    }
9622    VisibilityData {
9623        visible_by_file,
9624        visible_source_files_by_root,
9625    }
9626}
9627
9628/// Admit the class that an out-of-line definition proves is in scope.
9629///
9630/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
9631/// names a class-like entity in that file's scope: a member declaration can
9632/// live in a file other than its class's only when it is written out of line.
9633/// A file a build concatenates rather than compiles carries no `#include` edge
9634/// to the header declaring `Owner` -- google/wuffs
9635/// `internal/cgen/auxiliary/image.cc` defines
9636/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
9637/// every unqualified member and constructor reference in it had no candidate at
9638/// all (#1832).
9639///
9640/// The evidence is the indexed declaration's own owner name, taken from its
9641/// `FqName`, so this stays a structured answer rather than a text fallback.
9642/// Only an owner the file cannot already see is admitted: that is what keeps a
9643/// header declaring its own class from additionally seeing every same-named
9644/// class in the workspace, and it makes the pass free for the ordinary file
9645/// whose owners are all visible.
9646#[derive(Default)]
9647struct OutOfLineOwnerBindingStats {
9648    unseen_owners: usize,
9649    definition_lookups: usize,
9650    admitted: usize,
9651}
9652
9653fn extend_with_out_of_line_owner_bindings(
9654    cpp: &dyn CppSource,
9655    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9656) -> OutOfLineOwnerBindingStats {
9657    let mut stats = OutOfLineOwnerBindingStats::default();
9658    for (file, visible) in visible_by_file.iter_mut() {
9659        // The include-closure walk seeds every root with its own declarations,
9660        // so the file's members are already here; re-reading them from the
9661        // analyzer would pay for the same declaration set twice.
9662        let mut unseen_owners: HashSet<String> = visible
9663            .iter()
9664            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9665            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9666            .collect();
9667        if unseen_owners.is_empty() {
9668            continue;
9669        }
9670        for unit in visible.iter().filter(|unit| unit.is_class()) {
9671            unseen_owners.remove(&unit.fq_name());
9672        }
9673        stats.unseen_owners += unseen_owners.len();
9674        stats.definition_lookups += unseen_owners.len();
9675        let admitted = unseen_owners
9676            .iter()
9677            .flat_map(|owner| cpp.definitions(owner))
9678            .filter(CodeUnit::is_class)
9679            .collect::<Vec<_>>();
9680        stats.admitted += admitted.len();
9681        visible.extend(admitted);
9682    }
9683    stats
9684}
9685
9686pub enum VisibleMemberResolution {
9687    Callable(Vec<CodeUnit>),
9688    NonCallable,
9689    AmbiguousKind,
9690    Missing,
9691}
9692
9693#[derive(Clone)]
9694pub enum EnclosingMemberOwnerResolution {
9695    Owner(CodeUnit),
9696    Ambiguous,
9697    Missing,
9698}
9699
9700pub fn resolve_declaring_member_owner(
9701    analyzer: &CppGraphSource<'_>,
9702    visibility: &VisibilityIndex<'_>,
9703    file: &ProjectFile,
9704    receiver_owner: &CodeUnit,
9705    member_name: &str,
9706) -> EnclosingMemberOwnerResolution {
9707    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9708        return EnclosingMemberOwnerResolution::Missing;
9709    };
9710    let Some(receiver_owner) =
9711        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9712    else {
9713        return EnclosingMemberOwnerResolution::Ambiguous;
9714    };
9715    let resolve_level = |frontier: &[CodeUnit]| {
9716        let mut member_owners = Vec::new();
9717        for raw_owner in frontier {
9718            let Some(owner) =
9719                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9720            else {
9721                return EnclosingMemberOwnerResolution::Ambiguous;
9722            };
9723            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9724                // A type nested in an owner shares the owner's member-name
9725                // index, but it cannot be the value receiver of `owner.name`.
9726                // Keep value members here so an anonymous aggregate field
9727                // does not become ambiguous with its promoted receiver type.
9728                if !member.is_field() && !member.is_function() {
9729                    continue;
9730                }
9731                let Some(member_owner) = type_owner_of(analyzer, member) else {
9732                    return EnclosingMemberOwnerResolution::Ambiguous;
9733                };
9734                if !member_owners
9735                    .iter()
9736                    .any(|existing| same_visible_symbol(existing, &member_owner))
9737                {
9738                    member_owners.push(member_owner);
9739                }
9740            }
9741        }
9742        match member_owners.len() {
9743            0 => EnclosingMemberOwnerResolution::Missing,
9744            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9745            _ => EnclosingMemberOwnerResolution::Ambiguous,
9746        }
9747    };
9748    // The first declaration on each structured base path hides deeper names,
9749    // regardless of whether its callable overload is applicable at a particular
9750    // call site. Applicability is checked only after this owner is established.
9751    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9752    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9753        return direct;
9754    }
9755    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9756    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9757    let mut path_matches = Vec::new();
9758    while let Some(raw_owner) = stack.pop() {
9759        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9760        else {
9761            return EnclosingMemberOwnerResolution::Ambiguous;
9762        };
9763        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
9764        // Propagate at most two occurrences of each owner: that preserves the distinction
9765        // between one and multiple resolving base paths without exponential diamond walks.
9766        let propagated = propagated_counts.entry(owner.clone()).or_default();
9767        if *propagated == 2 {
9768            continue;
9769        }
9770        *propagated += 1;
9771        match resolve_level(std::slice::from_ref(&owner)) {
9772            EnclosingMemberOwnerResolution::Owner(owner) => {
9773                path_matches.push(owner);
9774                if path_matches.len() == 2 {
9775                    return EnclosingMemberOwnerResolution::Ambiguous;
9776                }
9777            }
9778            EnclosingMemberOwnerResolution::Ambiguous => {
9779                return EnclosingMemberOwnerResolution::Ambiguous;
9780            }
9781            EnclosingMemberOwnerResolution::Missing => {
9782                stack.extend(hierarchy.get_direct_ancestors(&owner));
9783            }
9784        }
9785    }
9786    match path_matches.len() {
9787        0 => EnclosingMemberOwnerResolution::Missing,
9788        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
9789        _ => unreachable!("base-path matches are capped at one before returning"),
9790    }
9791}
9792
9793/// Resolve the declaring owner of a callable after applying a member
9794/// `using <Base>::<member>;` declaration to one exact call arity.
9795///
9796/// Ordinary member lookup is intentionally name-based: the first class that
9797/// declares a name hides the same name on deeper bases. A member
9798/// using-declaration is the one exception. When none of the declarations on
9799/// that first owner accepts the call arity, it can reintroduce an applicable
9800/// overload from the named base. If a declaration on the first owner does
9801/// accept the arity, argument types would be needed to choose between it and
9802/// a same-arity introduced overload, so this resolver conservatively keeps the
9803/// ordinary owner (#1835/#1843).
9804///
9805/// The caller supplies ordinary name-based owner resolution so a file scan can
9806/// reuse its existing owner cache before applying this callable-only exception.
9807pub fn resolve_declaring_callable_owner(
9808    analyzer: &CppGraphSource<'_>,
9809    visibility: &VisibilityIndex<'_>,
9810    file: &ProjectFile,
9811    ordinary: EnclosingMemberOwnerResolution,
9812    member_name: &str,
9813    call_arity: usize,
9814) -> EnclosingMemberOwnerResolution {
9815    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
9816        return ordinary;
9817    };
9818    if visibility
9819        .visible_members_for_owner_name(file, ordinary_owner, member_name)
9820        .into_iter()
9821        .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
9822    {
9823        return ordinary;
9824    }
9825
9826    let mut pending = match member_using_declaration_bases(
9827        analyzer,
9828        visibility,
9829        file,
9830        ordinary_owner,
9831        member_name,
9832    ) {
9833        Ok(bases) => bases,
9834        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9835    };
9836    let mut visited = HashSet::default();
9837    let mut introduced_owners = Vec::new();
9838    while let Some(owner) = pending.pop() {
9839        if !visited.insert(owner.clone()) {
9840            continue;
9841        }
9842        let accepts_arity = visibility
9843            .visible_members_for_owner_name(file, &owner, member_name)
9844            .into_iter()
9845            .any(|unit| {
9846                unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
9847            });
9848        if accepts_arity {
9849            if !introduced_owners
9850                .iter()
9851                .any(|existing| same_visible_symbol(existing, &owner))
9852            {
9853                introduced_owners.push(owner);
9854            }
9855            continue;
9856        }
9857        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
9858            Ok(bases) => pending.extend(bases),
9859            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9860        }
9861    }
9862    match introduced_owners.as_slice() {
9863        [] => ordinary,
9864        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
9865        _ => EnclosingMemberOwnerResolution::Ambiguous,
9866    }
9867}
9868
9869fn member_using_declaration_bases(
9870    analyzer: &CppGraphSource<'_>,
9871    visibility: &VisibilityIndex<'_>,
9872    file: &ProjectFile,
9873    owner: &CodeUnit,
9874    member_name: &str,
9875) -> Result<Vec<CodeUnit>, ()> {
9876    let Some(source) = analyzer.get_source(owner, false) else {
9877        return Ok(Vec::new());
9878    };
9879    let scopes = cpp_member_using_declaration_scopes(&source, member_name);
9880    if scopes.is_empty() {
9881        return Ok(Vec::new());
9882    }
9883    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9884        return Ok(Vec::new());
9885    };
9886    let mut bases = Vec::new();
9887    for raw_ancestor in hierarchy.get_ancestors(owner) {
9888        let Some(ancestor) =
9889            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
9890        else {
9891            return Err(());
9892        };
9893        let qualified = cpp_name_for(&ancestor);
9894        if scopes
9895            .iter()
9896            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
9897            && !bases
9898                .iter()
9899                .any(|existing| same_visible_symbol(existing, &ancestor))
9900        {
9901            bases.push(ancestor);
9902        }
9903    }
9904    Ok(bases)
9905}
9906
9907pub fn lexical_component_tiers<'a>(
9908    components: &'a [String],
9909    global: bool,
9910    lexical_scope: &'a [String],
9911) -> impl Iterator<Item = Vec<String>> + 'a {
9912    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
9913    (0..=first_prefix_len).rev().map(move |prefix_len| {
9914        let mut qualified = Vec::with_capacity(prefix_len + components.len());
9915        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
9916        qualified.extend_from_slice(components);
9917        qualified
9918    })
9919}
9920
9921pub fn build_visible_identifier_index(
9922    analyzer: &CppGraphSource<'_>,
9923    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
9924    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9925    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
9926) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
9927    let mut out = HashMap::default();
9928    for (file, visible) in visible_by_file {
9929        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9930        for unit in visible {
9931            if unit.is_field()
9932                && !visible_source_files_by_root
9933                    .get(file)
9934                    .is_some_and(|sources| sources.contains(unit.source()))
9935                && cpp_global_field_has_internal_linkage_cached(
9936                    analyzer,
9937                    global_field_internal_linkage,
9938                    unit,
9939                )
9940            {
9941                continue;
9942            }
9943            by_identifier
9944                .entry(unit.identifier().to_string())
9945                .or_default()
9946                .push(unit.clone());
9947        }
9948        for units in by_identifier.values_mut() {
9949            sort_lookup_units(units);
9950            units.dedup();
9951        }
9952        out.insert(file.clone(), by_identifier);
9953    }
9954    out
9955}
9956
9957fn sort_lookup_units(units: &mut [CodeUnit]) {
9958    units.sort_by(|left, right| {
9959        left.fq_name()
9960            .cmp(&right.fq_name())
9961            .then_with(|| left.signature().cmp(&right.signature()))
9962            .then_with(|| left.source().cmp(right.source()))
9963            .then_with(|| left.kind().cmp(&right.kind()))
9964            .then_with(|| {
9965                left.package_segment_count()
9966                    .cmp(&right.package_segment_count())
9967            })
9968            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
9969            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
9970    });
9971}
9972
9973fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
9974    let interner = segment_interner();
9975    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
9976        let (left_text, left_kind) = interner.resolve(left_id);
9977        let (right_text, right_kind) = interner.resolve(right_id);
9978        let order = left_text
9979            .cmp(right_text)
9980            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
9981        if order != CmpOrdering::Equal {
9982            return order;
9983        }
9984    }
9985    left.len().cmp(&right.len())
9986}
9987
9988const fn segment_kind_order(kind: SegmentKind) -> u8 {
9989    match kind {
9990        SegmentKind::Path => 0,
9991        SegmentKind::Package => 1,
9992        SegmentKind::Type => 2,
9993        SegmentKind::Companion => 3,
9994        SegmentKind::Nested => 4,
9995        SegmentKind::Member => 5,
9996        SegmentKind::Unknown => 6,
9997    }
9998}
9999
10000fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10001    let mut deduped = Vec::with_capacity(units.len());
10002    for unit in units.drain(..) {
10003        if !deduped.contains(&unit) {
10004            deduped.push(unit);
10005        }
10006    }
10007    *units = deduped;
10008}
10009
10010pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10011    // Same domain as `candidate_units` above: `reference` is a plain
10012    // `::`-joined qualified-id with operator tokens kept intact by the shared
10013    // splitter's operator merge.
10014    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10015        brokk_bifrost_core::analyzer::Language::Cpp,
10016        reference,
10017    );
10018    if parts.is_empty() {
10019        return Vec::new();
10020    }
10021
10022    let mut candidates = Vec::new();
10023    for package_len in 0..parts.len() {
10024        let package = parts[..package_len].join("::");
10025        let rest = &parts[package_len..];
10026        if rest.is_empty() {
10027            continue;
10028        }
10029        match kind {
10030            TargetKind::Type | TargetKind::Constructor => {
10031                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10032                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10033            }
10034            TargetKind::FreeFunction
10035            | TargetKind::Method
10036            | TargetKind::GlobalField
10037            | TargetKind::MemberField
10038            | TargetKind::Macro => {
10039                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10040                if rest.len() > 1 {
10041                    let owner = rest[..rest.len() - 1].join("$");
10042                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10043                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
10044                }
10045            }
10046        }
10047    }
10048    candidates
10049}
10050
10051fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10052    let fqn = if package.is_empty() {
10053        short.to_string()
10054    } else {
10055        format!("{package}.{short}")
10056    };
10057    if !out.contains(&fqn) {
10058        out.push(fqn);
10059    }
10060}
10061
10062pub fn infer_cpp_initializer_type(
10063    analyzer: &CppGraphSource<'_>,
10064    visibility: &VisibilityIndex<'_>,
10065    file: &ProjectFile,
10066    source: &str,
10067    node: Node<'_>,
10068) -> Option<CodeUnit> {
10069    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10070        .and_then(|binding| binding.unit)
10071}
10072
10073pub fn infer_cpp_initializer_binding(
10074    analyzer: &CppGraphSource<'_>,
10075    visibility: &VisibilityIndex<'_>,
10076    file: &ProjectFile,
10077    source: &str,
10078    node: Node<'_>,
10079    receiver_resolver: Option<&ReceiverResolver<'_>>,
10080) -> Option<CppScanBinding> {
10081    match node.kind() {
10082        "new_expression" => {
10083            let text = normalize_cpp_whitespace(node_text(node, source));
10084            let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
10085            let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
10086            let name = normalize_cpp_type_name(type_text);
10087            Some(CppScanBinding::from_type_name(
10088                name.clone(),
10089                visibility.resolve_type(file, &name),
10090                1,
10091            ))
10092        }
10093        "call_expression" => node.child_by_field_name("function").and_then(|function| {
10094            // `a().b()` and `p->b()` invoke a member on a receiver *value*. The
10095            // callee's source text is an expression, not a name, and every name
10096            // lookup below normalizes a reference by truncating at the first
10097            // `(`: `first().second` would read as `first`, so the chained call
10098            // would take the type of `first()` instead of the type of
10099            // `first().second()` (#2178). Only the member path can answer for
10100            // this shape, so route to it from the callee's node kind.
10101            if function.kind() == "field_expression" {
10102                let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10103                return resolve_field_method_call_return_binding(
10104                    analyzer,
10105                    visibility,
10106                    file,
10107                    source,
10108                    function,
10109                    arity,
10110                    receiver_resolver,
10111                );
10112            }
10113            let function_text = node_text(function, source);
10114            let direct_type_binding = visibility
10115                .resolve_type(file, function_text)
10116                .map(|unit| CppScanBinding::from_unit(unit, 0));
10117            if function.kind() == "template_function" && direct_type_binding.is_some() {
10118                let lexical_namespace = enclosing_namespace_context(node, source);
10119                let arity = visibility.call_arity_evidence(file, node, source).exact();
10120                if let Some(arity) = arity
10121                    && let Some(binding) = visibility.resolve_call_return_binding(
10122                        analyzer,
10123                        file,
10124                        function_text,
10125                        arity,
10126                        lexical_namespace.as_deref(),
10127                        direct_type_binding
10128                            .as_ref()
10129                            .and_then(|binding| binding.unit.as_ref()),
10130                    )
10131                {
10132                    return Some(binding);
10133                }
10134                let (has_callable, callable_binding) = visibility
10135                    .resolve_call_return_binding_without_arity(
10136                        analyzer,
10137                        file,
10138                        function_text,
10139                        lexical_namespace.as_deref(),
10140                        direct_type_binding
10141                            .as_ref()
10142                            .and_then(|binding| binding.unit.as_ref()),
10143                    );
10144                if let Some(binding) = callable_binding {
10145                    return Some(binding);
10146                }
10147                if has_callable {
10148                    return None;
10149                }
10150                return direct_type_binding;
10151            }
10152            // Only the return-typed branches need the argument count. An
10153            // unknown arity leaves them out, exactly as in the template arm
10154            // above, and still constructs the direct type: `File(getPath())`
10155            // names `File` whether or not `getPath()`'s expansion is provable.
10156            let arity = visibility.call_arity_evidence(file, node, source).exact();
10157            if let Some(arity) = arity {
10158                let direct_type_binding_for_call = direct_type_binding.clone();
10159                if let Some(binding) = resolve_static_method_call_return_binding(
10160                    analyzer, visibility, file, source, function, arity,
10161                )
10162                .or_else(|| {
10163                    // An applicable free function supplies the receiver value
10164                    // before an unrelated visible type with the same terminal
10165                    // name. The direct type still excludes its own constructor
10166                    // declaration below and remains the construction fallback.
10167                    visibility.resolve_call_return_binding(
10168                        analyzer,
10169                        file,
10170                        function_text,
10171                        arity,
10172                        enclosing_namespace_context(node, source).as_deref(),
10173                        direct_type_binding_for_call
10174                            .as_ref()
10175                            .and_then(|binding| binding.unit.as_ref()),
10176                    )
10177                }) {
10178                    return Some(binding);
10179                }
10180            }
10181            direct_type_binding
10182        }),
10183        _ => None,
10184    }
10185}
10186
10187fn resolve_static_method_call_return_binding(
10188    analyzer: &CppGraphSource<'_>,
10189    visibility: &VisibilityIndex<'_>,
10190    file: &ProjectFile,
10191    source: &str,
10192    function: Node<'_>,
10193    arity: usize,
10194) -> Option<CppScanBinding> {
10195    if function.kind() != "qualified_identifier" {
10196        return None;
10197    }
10198    let qualified = normalize_cpp_reference_text(node_text(function, source));
10199    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
10200    // single component (the shared splitter's operator-token merge keeps
10201    // `operator+`-style names intact), so re-tokenizing with the shared
10202    // structured splitter and peeling the terminal segment reproduces
10203    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
10204    // `cpp_out_of_line_function_owner`'s `qualified` split above.
10205    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10206        brokk_bifrost_core::analyzer::Language::Cpp,
10207        &qualified,
10208    );
10209    let (owner_text, member_name) = match parts.split_last() {
10210        Some((member, owner_parts)) if !owner_parts.is_empty() => {
10211            (owner_parts.join("::"), member.clone())
10212        }
10213        _ => {
10214            let scope = function.child_by_field_name("scope")?;
10215            let name = function.child_by_field_name("name")?;
10216            (
10217                node_text(scope, source).to_string(),
10218                node_text(name, source).to_string(),
10219            )
10220        }
10221    };
10222    let owner = visibility.resolve_type(file, &owner_text)?;
10223    let candidates = visibility
10224        .visible_members_for_owner_name(file, &owner, &member_name)
10225        .into_iter()
10226        .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
10227        .cloned()
10228        .collect::<Vec<_>>();
10229    unanimous_return_binding(analyzer, visibility, file, &candidates)
10230}
10231
10232fn resolve_field_method_call_return_binding(
10233    analyzer: &CppGraphSource<'_>,
10234    visibility: &VisibilityIndex<'_>,
10235    file: &ProjectFile,
10236    source: &str,
10237    function: Node<'_>,
10238    arity: usize,
10239    receiver_resolver: Option<&ReceiverResolver<'_>>,
10240) -> Option<CppScanBinding> {
10241    debug_assert_eq!(
10242        function.kind(),
10243        "field_expression",
10244        "the member-call return binding answers only for a field-expression callee"
10245    );
10246    let receiver_resolver = receiver_resolver?;
10247    let field = function.child_by_field_name("field")?;
10248    let member_name = node_text(function_terminal_node(field), source);
10249    let receiver = function
10250        .child_by_field_name("argument")
10251        .or_else(|| function.named_child(0))?;
10252    let owners = receiver_resolver(receiver, source);
10253    let mut candidates = Vec::new();
10254    for owner in owners {
10255        let declaring_owner =
10256            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10257                EnclosingMemberOwnerResolution::Owner(owner) => owner,
10258                EnclosingMemberOwnerResolution::Missing => continue,
10259                EnclosingMemberOwnerResolution::Ambiguous => return None,
10260            };
10261        candidates.extend(
10262            visibility
10263                .visible_members_for_owner_name(file, &declaring_owner, member_name)
10264                .into_iter()
10265                .filter(|unit| {
10266                    unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
10267                })
10268                .cloned(),
10269        );
10270    }
10271    unanimous_return_binding(analyzer, visibility, file, &candidates)
10272}
10273
10274fn unanimous_return_binding(
10275    analyzer: &CppGraphSource<'_>,
10276    visibility: &VisibilityIndex<'_>,
10277    file: &ProjectFile,
10278    candidates: &[CodeUnit],
10279) -> Option<CppScanBinding> {
10280    let mut resolved_return: Option<CppScanBinding> = None;
10281    for function in candidates {
10282        let metadata = analyzer.signature_metadata(function);
10283        let return_types = if metadata.is_empty() {
10284            vec![cpp_function_return_type_text(analyzer, function)?]
10285        } else {
10286            metadata
10287                .iter()
10288                .map(|metadata| metadata.return_type_text().map(str::to_string))
10289                .collect::<Option<Vec<_>>>()?
10290        };
10291        for return_text in return_types {
10292            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10293            let name = normalize_cpp_type_name(&return_text);
10294            let binding = CppScanBinding::from_type_name(
10295                name.clone(),
10296                visibility
10297                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10298                indirection,
10299            );
10300            if let Some(existing) = resolved_return.as_ref()
10301                && (existing.indirection != binding.indirection
10302                    || match (&existing.unit, &binding.unit) {
10303                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
10304                        (None, None) => existing.type_name != binding.type_name,
10305                        (Some(_), None) | (None, Some(_)) => true,
10306                    })
10307            {
10308                return None;
10309            }
10310            resolved_return = Some(binding);
10311        }
10312    }
10313    resolved_return
10314}
10315
10316fn aliases_from_prepared_source(
10317    cpp: &dyn CppSource,
10318    token: QueryToken<'_>,
10319    file: &ProjectFile,
10320) -> Vec<CppAlias> {
10321    let Some(prepared) = cpp.prepared_syntax(token, file) else {
10322        return Vec::new();
10323    };
10324    let mut aliases = Vec::new();
10325    collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
10326    aliases
10327}
10328
10329fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
10330    walk_named_tree_preorder(root, true, |node| {
10331        match node.kind() {
10332            "alias_declaration" if alias_has_visible_file_scope(node) => {
10333                if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
10334                    out.push(alias);
10335                }
10336            }
10337            "type_definition" if alias_has_visible_file_scope(node) => {
10338                collect_typedef_aliases(node, source, out)
10339            }
10340            _ => {}
10341        }
10342        WalkControl::Continue
10343    });
10344}
10345
10346fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10347    let mut current = node.parent();
10348    while let Some(parent) = current {
10349        match parent.kind() {
10350            "translation_unit"
10351            | "namespace_definition"
10352            | "declaration_list"
10353            | "linkage_specification" => current = parent.parent(),
10354            "template_declaration" => current = parent.parent(),
10355            _ => return false,
10356        }
10357    }
10358    true
10359}
10360
10361fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
10362    let name = node
10363        .child_by_field_name("name")
10364        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
10365    let target = node
10366        .child_by_field_name("type")
10367        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
10368    Some(CppAlias {
10369        name,
10370        target,
10371        namespace: enclosing_namespace_context(node, source),
10372    })
10373}
10374
10375fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
10376    let Some(type_node) = node.child_by_field_name("type") else {
10377        return;
10378    };
10379    let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
10380        return;
10381    };
10382
10383    let mut cursor = node.walk();
10384    for child in node.named_children(&mut cursor) {
10385        if same_node(child, type_node) {
10386            continue;
10387        }
10388        if let Some(name) = extract_typedef_declarator_name(child, source) {
10389            out.push(CppAlias {
10390                name,
10391                target: target.clone(),
10392                namespace: enclosing_namespace_context(node, source),
10393            });
10394        }
10395    }
10396}
10397
10398fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
10399    match node.kind() {
10400        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
10401            normalize_reference_name(node_text(node, source))
10402        }
10403        _ => node
10404            .child_by_field_name("declarator")
10405            .or_else(|| node.child_by_field_name("name"))
10406            .or_else(|| last_named_child(node))
10407            .and_then(|child| extract_typedef_declarator_name(child, source)),
10408    }
10409}
10410
10411fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
10412    let count = node.named_child_count();
10413    if count == 0 {
10414        None
10415    } else {
10416        node.named_child(count - 1)
10417    }
10418}
10419
10420pub fn collect_include_closure(
10421    analyzer: &CppGraphSource<'_>,
10422    include_targets: &IncludeTargetIndex,
10423    file: &ProjectFile,
10424    out: &mut HashSet<ProjectFile>,
10425    cancellation: Option<&CancellationToken>,
10426) {
10427    let mut stack = vec![file.clone()];
10428    while let Some(file) = stack.pop() {
10429        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10430            break;
10431        }
10432        if !out.insert(file.clone()) {
10433            continue;
10434        }
10435        let imports = analyzer.import_statements(&file);
10436        for include in cpp_include_paths(&imports) {
10437            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10438                stack.push(target);
10439            }
10440        }
10441    }
10442}
10443
10444fn collect_visible_declarations(
10445    include_graph: &IncludeGraph,
10446    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10447    file: &ProjectFile,
10448    visited: &mut HashSet<ProjectFile>,
10449    out: &mut HashSet<CodeUnit>,
10450    cancellation: Option<&CancellationToken>,
10451) {
10452    let mut stack = vec![file.clone()];
10453    while let Some(file) = stack.pop() {
10454        if cancellation.is_some_and(CancellationToken::is_cancelled) {
10455            break;
10456        }
10457        if !visited.insert(file.clone()) {
10458            continue;
10459        }
10460        if let Some(declarations) = declarations_by_file.get(&file) {
10461            out.extend(declarations.iter().cloned());
10462        }
10463        stack.extend(include_graph.targets(&file).iter().cloned());
10464    }
10465}
10466
10467pub fn signature_arity(signature: Option<&str>) -> usize {
10468    let Some(signature) = signature else {
10469        return 0;
10470    };
10471    let inner = signature
10472        .find('(')
10473        .and_then(|open| {
10474            signature[open + 1..]
10475                .find(')')
10476                .map(|close| &signature[open + 1..open + 1 + close])
10477        })
10478        .unwrap_or(signature)
10479        .trim();
10480    if inner.is_empty() || inner == "void" {
10481        return 0;
10482    }
10483    cpp_split_top_level_commas(inner).count()
10484}
10485
10486fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10487    let source = format!("void __bifrost_macro_parameters({replacement});");
10488    let mut parser = Parser::new();
10489    parser
10490        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10491        .ok()?;
10492    let tree = parser.parse(&source, None)?;
10493    let root = tree.root_node();
10494    if root.has_error() {
10495        return None;
10496    }
10497    let declaration = root.named_child(0)?;
10498    let declarator = declaration.child_by_field_name("declarator")?;
10499    let parameters = declarator.child_by_field_name("parameters")?;
10500    let mut required = 0;
10501    let mut total = 0;
10502    let mut repeated = false;
10503    let mut cursor = parameters.walk();
10504    for parameter in parameters.children(&mut cursor) {
10505        match parameter.kind() {
10506            "parameter_declaration" => {
10507                if parameter.child_by_field_name("declarator").is_none()
10508                    && parameter
10509                        .child_by_field_name("type")
10510                        .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
10511                {
10512                    continue;
10513                }
10514                required += 1;
10515                total += 1;
10516            }
10517            "optional_parameter_declaration" => total += 1,
10518            "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
10519                repeated = true;
10520            }
10521            _ => {}
10522        }
10523    }
10524    Some(CallableArity::new(required, total, repeated))
10525}
10526
10527pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
10528    analyzer
10529        .signature_metadata(unit)
10530        .into_iter()
10531        .find_map(|metadata| metadata.callable_arity())
10532        .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
10533}
10534
10535pub fn cpp_callable_parameter_types(
10536    analyzer: &CppGraphSource<'_>,
10537    unit: &CodeUnit,
10538) -> Option<Vec<String>> {
10539    analyzer
10540        .signature_metadata(unit)
10541        .into_iter()
10542        .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
10543        .or_else(|| unit.signature().and_then(cpp_signature_param_types))
10544}
10545
10546fn merge_compatible_callable_arities(
10547    left: CallableArity,
10548    right: CallableArity,
10549) -> Option<CallableArity> {
10550    let total = left.total();
10551    let left_repeated = left.accepts(total.saturating_add(1));
10552    let right_repeated = right.accepts(right.total().saturating_add(1));
10553    if total != right.total() || left_repeated != right_repeated {
10554        return None;
10555    }
10556    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10557    Some(CallableArity::new(required, total, left_repeated))
10558}
10559
10560fn find_include_activation(
10561    cpp: &dyn CppSource,
10562    token: QueryToken<'_>,
10563    file: &ProjectFile,
10564    prepared: &PreparedSyntaxTree,
10565    donor_source: &ProjectFile,
10566) -> Option<usize> {
10567    let include_targets = cpp.include_target_index();
10568    let mut direct_includes = Vec::new();
10569    let mut nodes = vec![prepared.tree().root_node()];
10570    // An include activates for the whole file, so only an unconditional
10571    // directive counts here.
10572    let reference = CallableReferenceContext {
10573        file,
10574        position: None,
10575    };
10576    while let Some(node) = nodes.pop() {
10577        if node.kind() == "preproc_include" {
10578            if callable_preprocessor_context_is_visible_for_reference(
10579                node,
10580                prepared.source(),
10581                &reference,
10582            ) {
10583                let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10584                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10585                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10586                        file,
10587                        &include,
10588                        include_targets,
10589                    )) {
10590                        direct_includes.push((node.end_byte(), target));
10591                    }
10592                }
10593            }
10594            continue;
10595        }
10596        push_named_children_reversed(node, &mut nodes);
10597    }
10598    direct_includes.sort_by_key(|(activation, _)| *activation);
10599    let mut known_missing = HashSet::default();
10600    direct_includes
10601        .into_iter()
10602        .find(|(_, direct)| {
10603            unconditional_include_reaches(
10604                cpp,
10605                token,
10606                include_targets,
10607                direct,
10608                donor_source,
10609                file,
10610                &mut known_missing,
10611            )
10612        })
10613        .map(|(activation, _)| activation)
10614}
10615
10616fn find_conditional_include_projection_index(
10617    cpp: &dyn CppSource,
10618    token: QueryToken<'_>,
10619    file: &ProjectFile,
10620    prepared: &PreparedSyntaxTree,
10621    on_state: &dyn Fn(),
10622) -> ConditionalIncludeProjectionIndex {
10623    let reference_is_c = reference_uses_c_semantics(cpp, file);
10624    let include_targets = cpp.include_target_index();
10625    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10626        HashMap::default();
10627    let mut pending = Vec::new();
10628    let mut nodes = vec![prepared.tree().root_node()];
10629    while let Some(node) = nodes.pop() {
10630        if node.kind() == "preproc_include" {
10631            let Some(required_guards) =
10632                include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10633            else {
10634                continue;
10635            };
10636            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10637            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10638                let Some(target) = unique_include_target(resolve_include_targets_with_index(
10639                    file,
10640                    &include,
10641                    include_targets,
10642                )) else {
10643                    continue;
10644                };
10645                pending.push((target, node.end_byte(), required_guards.clone()));
10646            }
10647            continue;
10648        }
10649        push_named_children_reversed(node, &mut nodes);
10650    }
10651
10652    // One reached file can have several distinct compatible guard paths. Each
10653    // (file, activation byte) key keeps only the inclusion-minimal guard sets:
10654    // the consumers ask existence questions whose answers are monotone in the
10655    // guard set -- a path whose requirements hold, stay stable, and stay
10656    // compatible under one environment does so under every subset as well --
10657    // so a state subsumed by an existing subset cannot witness anything its
10658    // subset does not, and inserting a smaller set evicts the supersets it
10659    // subsumes. Exact-set dedup still terminated cycles, but dense `#ifdef`
10660    // lattices (QMK's per-keyboard feature guards) enumerated the powerset of
10661    // path-union guard sets through it: the state space, the per-key linear
10662    // scans, and resident memory all grew without bound (#2365).
10663    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10664        HashMap::default();
10665    while let Some((current_file, activation_byte, path)) = pending.pop() {
10666        let guard_sets = expanded
10667            .entry((current_file.clone(), activation_byte))
10668            .or_default();
10669        // Minimality is keyed on the full guard set alone: a path's undecided
10670        // guards are the per-conditional subset of that set, so a subset path
10671        // carries a subset of them too.
10672        if guard_sets
10673            .iter()
10674            .any(|existing| existing.is_subset(&path.all))
10675        {
10676            continue;
10677        }
10678        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10679            .drain(..)
10680            .partition(|existing| path.all.is_subset(existing));
10681        *guard_sets = kept;
10682        guard_sets.push(path.all.clone());
10683        if !evicted.is_empty()
10684            && let Some(projections) = projections_by_source.get_mut(&current_file)
10685        {
10686            projections.retain(|projection| {
10687                projection.activation_byte != activation_byte
10688                    || !evicted.contains(&projection.required_guards)
10689            });
10690        }
10691        on_state();
10692
10693        // A fresh minimal set has no equal in the store: equality would have
10694        // been caught by the subset check above.
10695        projections_by_source
10696            .entry(current_file.clone())
10697            .or_default()
10698            .push(ConditionalIncludeProjection {
10699                activation_byte,
10700                required_guards: path.all.clone(),
10701                partial_guards: path.partial.clone(),
10702            });
10703
10704        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
10705            continue;
10706        };
10707        let mut nodes = vec![current_prepared.tree().root_node()];
10708        while let Some(node) = nodes.pop() {
10709            if node.kind() == "preproc_include" {
10710                let Some(include_guards) = include_directive_guard_requirements(
10711                    node,
10712                    current_prepared.source(),
10713                    reference_is_c,
10714                ) else {
10715                    continue;
10716                };
10717                let Some(reached) = path.merged(&include_guards) else {
10718                    continue;
10719                };
10720                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
10721                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10722                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
10723                        &current_file,
10724                        &include,
10725                        include_targets,
10726                    )) else {
10727                        continue;
10728                    };
10729                    pending.push((target, activation_byte, reached.clone()));
10730                }
10731                continue;
10732            }
10733            push_named_children_reversed(node, &mut nodes);
10734        }
10735    }
10736
10737    projections_by_source
10738        .into_iter()
10739        .map(|(source, mut projections)| {
10740            projections.sort_by_key(|projection| projection.activation_byte);
10741            (source, Arc::from(projections))
10742        })
10743        .collect()
10744}
10745
10746/// Decide one conditional include target without materializing every source
10747/// reached by every guard combination. A path `admission` already rejects
10748/// cannot become admissible after adding nested include guards -- both rules
10749/// only ever add requirements -- so discard it before expanding the next
10750/// header.
10751#[allow(clippy::too_many_arguments)]
10752fn find_conditional_include_projection_for_source(
10753    cpp: &dyn CppSource,
10754    token: QueryToken<'_>,
10755    file: &ProjectFile,
10756    prepared: &PreparedSyntaxTree,
10757    donor_source: &ProjectFile,
10758    admission: IncludePathAdmission,
10759    reference_guards: Option<&HashSet<PreprocessorGuard>>,
10760    reference_byte: usize,
10761    on_state: &dyn Fn(),
10762) -> bool {
10763    let Some(reference_guards) = reference_guards else {
10764        return false;
10765    };
10766    let reference_is_c = reference_uses_c_semantics(cpp, file);
10767    let include_targets = cpp.include_target_index();
10768    let mut pending = Vec::new();
10769    let mut nodes = vec![prepared.tree().root_node()];
10770    while let Some(node) = nodes.pop() {
10771        if node.kind() == "preproc_include" {
10772            let Some(required_guards) =
10773                include_directive_guard_requirements(node, prepared.source(), reference_is_c)
10774            else {
10775                continue;
10776            };
10777            if node.end_byte() > reference_byte
10778                || !admission.admits(
10779                    &required_guards.all,
10780                    &required_guards.partial,
10781                    Some(reference_guards),
10782                )
10783            {
10784                continue;
10785            }
10786            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10787            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10788                let Some(target) = unique_include_target(resolve_include_targets_with_index(
10789                    file,
10790                    &include,
10791                    include_targets,
10792                )) else {
10793                    continue;
10794                };
10795                if &target == donor_source {
10796                    return true;
10797                }
10798                pending.push((target, required_guards.clone()));
10799            }
10800            continue;
10801        }
10802        push_named_children_reversed(node, &mut nodes);
10803    }
10804
10805    let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
10806    while let Some((current_file, path)) = pending.pop() {
10807        let guard_sets = expanded.entry(current_file.clone()).or_default();
10808        if guard_sets.contains(&path.all) {
10809            continue;
10810        }
10811        guard_sets.push(path.all.clone());
10812        on_state();
10813
10814        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
10815            continue;
10816        };
10817        let mut nodes = vec![current_prepared.tree().root_node()];
10818        while let Some(node) = nodes.pop() {
10819            if node.kind() == "preproc_include" {
10820                let Some(include_guards) = include_directive_guard_requirements(
10821                    node,
10822                    current_prepared.source(),
10823                    reference_is_c,
10824                ) else {
10825                    continue;
10826                };
10827                let Some(reached) = path.merged(&include_guards) else {
10828                    continue;
10829                };
10830                if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
10831                    continue;
10832                }
10833                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
10834                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10835                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
10836                        &current_file,
10837                        &include,
10838                        include_targets,
10839                    )) else {
10840                        continue;
10841                    };
10842                    if &target == donor_source {
10843                        return true;
10844                    }
10845                    pending.push((target, reached.clone()));
10846                }
10847                continue;
10848            }
10849            push_named_children_reversed(node, &mut nodes);
10850        }
10851    }
10852    false
10853}
10854
10855/// Whether `translation_unit`'s unconditional `#include` closure reaches
10856/// `header`, directly or through any chain of headers.
10857///
10858/// The include-closure question asked on its own, for
10859/// [`crate::identity::cpp_header_body_files_are_related`]. The walk resolves
10860/// each include the way visibility does -- to a unique target or to nothing --
10861/// so a duplicated basename relates nothing, and it is memoized per file pair
10862/// on the analyzer.
10863///
10864/// The reference position is `translation_unit` itself: the question is
10865/// whether that unit compiles the header, so that unit's own dialect and
10866/// preprocessor context govern the walk.
10867pub fn cpp_include_closure_reaches(
10868    cpp: &dyn CppSource,
10869    token: QueryToken<'_>,
10870    translation_unit: &ProjectFile,
10871    header: &ProjectFile,
10872) -> bool {
10873    unconditional_include_reaches(
10874        cpp,
10875        token,
10876        cpp.include_target_index(),
10877        translation_unit,
10878        header,
10879        translation_unit,
10880        &mut HashSet::default(),
10881    )
10882}
10883
10884fn unconditional_include_reaches(
10885    cpp: &dyn CppSource,
10886    token: QueryToken<'_>,
10887    include_targets: &IncludeTargetIndex,
10888    first: &ProjectFile,
10889    donor_source: &ProjectFile,
10890    reference_file: &ProjectFile,
10891    known_missing: &mut HashSet<ProjectFile>,
10892) -> bool {
10893    if first == donor_source {
10894        return true;
10895    }
10896    if known_missing.contains(first) {
10897        return false;
10898    }
10899    let reference_is_c = reference_file
10900        .rel_path()
10901        .extension()
10902        .and_then(|extension| extension.to_str())
10903        == Some("c");
10904    if let Some(reaches) =
10905        cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
10906    {
10907        return reaches;
10908    }
10909    let mut visited = HashSet::default();
10910    let mut files = vec![first.clone()];
10911    // Only an unconditional directive extends the include reach, so the walk
10912    // asks the question without a reference position.
10913    let reference = CallableReferenceContext {
10914        file: reference_file,
10915        position: None,
10916    };
10917    while let Some(file) = files.pop() {
10918        if file == *donor_source {
10919            cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
10920            return true;
10921        }
10922        if known_missing.contains(&file) || !visited.insert(file.clone()) {
10923            continue;
10924        }
10925        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
10926            continue;
10927        };
10928        // One cursor for the whole-file walk; see `named_children_iter` (#3097).
10929        let mut cursor = prepared.tree().walk();
10930        let mut nodes = vec![prepared.tree().root_node()];
10931        while let Some(node) = nodes.pop() {
10932            if node.kind() == "preproc_include" {
10933                if callable_preprocessor_context_is_visible_for_reference(
10934                    node,
10935                    prepared.source(),
10936                    &reference,
10937                ) {
10938                    let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
10939                    for include in cpp_include_paths(std::slice::from_ref(&raw)) {
10940                        if let Some(target) = unique_include_target(
10941                            resolve_include_targets_with_index(&file, &include, include_targets),
10942                        ) {
10943                            files.push(target);
10944                        }
10945                    }
10946                }
10947                continue;
10948            }
10949            let first_pushed = nodes.len();
10950            nodes.extend(node.named_children(&mut cursor));
10951            nodes[first_pushed..].reverse();
10952        }
10953    }
10954    known_missing.extend(visited);
10955    cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
10956    false
10957}
10958
10959fn declaration_guard_requirements(
10960    analyzer: &CppGraphSource<'_>,
10961    cpp: &dyn CppSource,
10962    candidate: &CodeUnit,
10963) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
10964    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
10965        return Vec::new();
10966    };
10967    let root = prepared.tree().root_node();
10968    analyzer
10969        .ranges(candidate)
10970        .into_iter()
10971        .filter_map(|range| {
10972            root.descendant_for_byte_range(range.start_byte, range.end_byte)
10973                .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
10974                // A class name is injected into its own body at the declaration's
10975                // introduction point, not after the complete class range. Using
10976                // the start also preserves normal before/after ordering for aliases.
10977                .map(|required| (range.start_byte, required))
10978        })
10979        .collect()
10980}
10981
10982fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
10983    analyzer
10984        .ranges(candidate)
10985        .into_iter()
10986        .map(|range| range.start_byte)
10987        .min()
10988}
10989
10990/// The macro names every configuration in `contexts` defines -- the fact set
10991/// one file's compile-database coverage proves (#2011). `None` when the
10992/// database has no entry for the file, which is different from an empty
10993/// intersection: no entry means no coverage, while an empty intersection is
10994/// covered-and-proves-nothing.
10995fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
10996    let (first, rest) = contexts.split_first()?;
10997    Some(
10998        first
10999            .defined_macros
11000            .iter()
11001            .filter(|name| {
11002                rest.iter()
11003                    .all(|context| context.defined_macros.contains(*name))
11004            })
11005            .cloned()
11006            .collect(),
11007    )
11008}
11009
11010pub fn guard_requirements_hold_at_reference(
11011    required: &HashSet<PreprocessorGuard>,
11012    reference: Option<&HashSet<PreprocessorGuard>>,
11013) -> bool {
11014    reference.is_some_and(|active| {
11015        required
11016            .iter()
11017            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11018    })
11019}
11020
11021fn preprocessor_guard_holds_at_reference(
11022    required: &PreprocessorGuard,
11023    active: &HashSet<PreprocessorGuard>,
11024) -> bool {
11025    if active.contains(required) {
11026        return true;
11027    }
11028    let active_expression = BooleanGuardExpression::all(
11029        active
11030            .iter()
11031            .filter_map(PreprocessorGuard::as_boolean_expression),
11032    );
11033    required
11034        .as_boolean_expression()
11035        .is_some_and(|required| active_expression.implies(&required))
11036}
11037
11038/// Cross-file guard rule: two guard sets are compatible when neither one
11039/// contradicts the other. Use this instead of the subset test whenever the
11040/// guards come from a foreign file, which resolves its own conditionals
11041/// independently of the reference.
11042fn guards_compatible_at_reference(
11043    declaration: &HashSet<PreprocessorGuard>,
11044    reference: Option<&HashSet<PreprocessorGuard>>,
11045) -> bool {
11046    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11047}
11048
11049/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
11050/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
11051/// conditional.
11052///
11053/// Two declarations of one name that report the same chain stand in different
11054/// branches of it, so at most one of them is compiled in any configuration.
11055/// They are alternate spellings of a single declaration, not competing
11056/// declarations, and navigation must not present them as an ambiguity.
11057pub fn preprocessor_conditional_family_range(
11058    root: Node<'_>,
11059    start_byte: usize,
11060    end_byte: usize,
11061) -> Option<(usize, usize)> {
11062    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11063    let mut ancestor = Some(node);
11064    while let Some(current) = ancestor {
11065        if is_preprocessor_conditional(current)
11066            && preprocessor_conditional_contains_descendant(current, node)
11067        {
11068            let family = preprocessor_conditional_family_root(current);
11069            return Some((family.start_byte(), family.end_byte()));
11070        }
11071        ancestor = current.parent();
11072    }
11073    None
11074}
11075
11076fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
11077    let mut ancestor = node.parent();
11078    while let Some(current) = ancestor {
11079        if is_preprocessor_conditional(current)
11080            && preprocessor_conditional_contains_descendant(current, node)
11081        {
11082            let family = preprocessor_conditional_family_root(current);
11083            if preprocessor_conditional_family_has_terminal_else(family) {
11084                return Some(family);
11085            }
11086        }
11087        ancestor = current.parent();
11088    }
11089    None
11090}
11091
11092fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11093    while let Some(parent) = conditional.parent() {
11094        let is_alternative = parent
11095            .child_by_field_name("alternative")
11096            .is_some_and(|alternative| {
11097                alternative.start_byte() == conditional.start_byte()
11098                    && alternative.end_byte() == conditional.end_byte()
11099            });
11100        if !is_alternative {
11101            break;
11102        }
11103        conditional = parent;
11104    }
11105    conditional
11106}
11107
11108fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
11109    loop {
11110        let Some(alternative) = conditional.child_by_field_name("alternative") else {
11111            return false;
11112        };
11113        match alternative.kind() {
11114            "preproc_else" => return true,
11115            "preproc_elif" => conditional = alternative,
11116            _ => return false,
11117        }
11118    }
11119}
11120
11121/// The undecidable preprocessor guards an `#include` directive stands under,
11122/// or `None` when its conditional context keeps the directive out of a
11123/// translation unit of the reference's language: a false constant, a
11124/// condition this analyzer cannot read, or a `__cplusplus` branch that
11125/// language never takes.
11126///
11127/// This is [`callable_declaration_guard_requirements`]'s rule for the include
11128/// edge, and the language test is what keeps
11129/// [`IncludePathAdmission::Compatible`] honest: an empty active guard set
11130/// never implies `defined(__cplusplus)`, but it never contradicts it either,
11131/// so without this a `.c` file would reach the C++ arm of every
11132/// `#ifdef __cplusplus` / `#else` include pair.
11133fn include_directive_guard_requirements(
11134    node: Node<'_>,
11135    source: &str,
11136    reference_is_c: bool,
11137) -> Option<PreprocessorGuardEnvironment> {
11138    let required = preprocessor_guard_environment_by_family(node, source)?;
11139    let excluded_by_language = required.all.iter().any(|guard| match guard {
11140        PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11141        PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11142        _ => false,
11143    });
11144    (!excluded_by_language).then_some(required)
11145}
11146
11147pub fn preprocessor_guard_environment(
11148    node: Node<'_>,
11149    source: &str,
11150) -> Option<HashSet<PreprocessorGuard>> {
11151    preprocessor_guard_environment_by_family(node, source).map(|environment| environment.all)
11152}
11153
11154/// The preprocessor guards `node` stands under, split by whether the
11155/// conditional that contributes each one belongs to a family that covers every
11156/// configuration.
11157///
11158/// A complete `#if`/`#else` or `#if`/`#elif`/`#else` family is a case analysis
11159/// every configuration takes exactly one branch of, which is why the resolver
11160/// already reads a declaration through one (`exhaustive_guard_family_activation`,
11161/// `complementary_same_fqn_type_declarations`). A lone `#if` is not: nothing in
11162/// the source says any configuration defines that macro, so only the build can
11163/// decide it, and it stays in `partial`.
11164#[derive(Clone)]
11165struct PreprocessorGuardEnvironment {
11166    all: HashSet<PreprocessorGuard>,
11167    partial: HashSet<PreprocessorGuard>,
11168}
11169
11170impl PreprocessorGuardEnvironment {
11171    /// The environment of a path standing under both `self` and `other`, or
11172    /// `None` when the two contradict. A guard a lone `#if` contributes on
11173    /// either side stays undecided for the whole path.
11174    fn merged(&self, other: &Self) -> Option<Self> {
11175        Some(Self {
11176            all: merge_preprocessor_guards(&self.all, &other.all)?,
11177            partial: self.partial.union(&other.partial).cloned().collect(),
11178        })
11179    }
11180}
11181
11182fn preprocessor_guard_environment_by_family(
11183    node: Node<'_>,
11184    source: &str,
11185) -> Option<PreprocessorGuardEnvironment> {
11186    let mut all = HashSet::default();
11187    let mut partial = HashSet::default();
11188    let mut ancestor = node.parent();
11189    while let Some(conditional) = ancestor {
11190        if matches!(
11191            conditional.kind(),
11192            "preproc_if" | "preproc_ifdef" | "preproc_elif"
11193        ) && !is_file_covering_include_guard(conditional, source)
11194            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11195            && preprocessor_conditional_contains_descendant(conditional, node)
11196        {
11197            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11198            match guard {
11199                PreprocessorGuard::Constant(true) => {
11200                    ancestor = conditional.parent();
11201                    continue;
11202                }
11203                PreprocessorGuard::Constant(false) => return None,
11204                _ => {}
11205            }
11206            if all.contains(&guard.negated()) {
11207                return None;
11208            }
11209            if !preprocessor_conditional_family_has_terminal_else(
11210                preprocessor_conditional_family_root(conditional),
11211            ) {
11212                partial.insert(guard.clone());
11213            }
11214            all.insert(guard);
11215        }
11216        ancestor = conditional.parent();
11217    }
11218    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11219        match guard {
11220            PreprocessorGuard::Constant(true) => {}
11221            PreprocessorGuard::Constant(false) => return None,
11222            _ => {
11223                if all.contains(&guard.negated()) {
11224                    return None;
11225                }
11226                // The family of a conditional the parser split across a
11227                // statement boundary is not readable from the tree, so it
11228                // counts as undecided.
11229                partial.insert(guard.clone());
11230                all.insert(guard);
11231            }
11232        }
11233    }
11234    Some(PreprocessorGuardEnvironment { all, partial })
11235}
11236
11237fn fragmented_statement_preprocessor_guard(
11238    descendant: Node<'_>,
11239    source: &str,
11240) -> Option<PreprocessorGuard> {
11241    // A conditional that starts before `} else if (...) {` crosses the
11242    // enclosing statement's grammar boundary. tree-sitter leaves its opener
11243    // as a `preproc_if` with a missing terminator in the consequence and
11244    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
11245    // those structured nodes before restoring the guard to intervening uses.
11246    let mut ancestor = descendant.parent();
11247    while let Some(statement) = ancestor {
11248        if statement.kind() == "if_statement"
11249            && let (Some(consequence), Some(alternative)) = (
11250                statement.child_by_field_name("consequence"),
11251                statement.child_by_field_name("alternative"),
11252            )
11253            && alternative.start_byte() <= descendant.start_byte()
11254            && descendant.end_byte() <= alternative.end_byte()
11255        {
11256            let mut cursor = consequence.walk();
11257            let openers = consequence
11258                .named_children(&mut cursor)
11259                .filter(|child| {
11260                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11261                        && child
11262                            .child(child.child_count().saturating_sub(1))
11263                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11264                })
11265                .collect::<Vec<_>>();
11266            if openers.len() != 1 {
11267                ancestor = statement.parent();
11268                continue;
11269            }
11270
11271            let mut terminators = Vec::new();
11272            let mut stack = vec![alternative];
11273            while let Some(node) = stack.pop() {
11274                if node.kind() == "preproc_call"
11275                    && node.start_byte() >= descendant.end_byte()
11276                    && node
11277                        .child_by_field_name("directive")
11278                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11279                {
11280                    terminators.push(node);
11281                    continue;
11282                }
11283                push_named_children_reversed(node, &mut stack);
11284            }
11285            if terminators.len() == 1 {
11286                return simple_preprocessor_guard(openers[0], source);
11287            }
11288        }
11289        ancestor = statement.parent();
11290    }
11291    None
11292}
11293
11294fn preprocessor_guard_for_descendant(
11295    conditional: Node<'_>,
11296    descendant: Node<'_>,
11297    source: &str,
11298) -> Option<PreprocessorGuard> {
11299    let mut guard = simple_preprocessor_guard(conditional, source)?;
11300    if conditional
11301        .child_by_field_name("alternative")
11302        .is_some_and(|alternative| {
11303            alternative.start_byte() <= descendant.start_byte()
11304                && descendant.end_byte() <= alternative.end_byte()
11305        })
11306    {
11307        let alternative = conditional.child_by_field_name("alternative")?;
11308        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
11309        // descendant in any later branch must first exclude the parent branch,
11310        // then collect the nested `preproc_elif` guard from its own ancestor.
11311        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
11312            return None;
11313        }
11314        guard = guard.negated();
11315    }
11316    Some(guard)
11317}
11318
11319fn preprocessor_conditional_contains_descendant(
11320    conditional: Node<'_>,
11321    descendant: Node<'_>,
11322) -> bool {
11323    cpp_displaced_preprocessor_boundary(conditional)
11324        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11325}
11326
11327pub fn merge_preprocessor_guards(
11328    left: &HashSet<PreprocessorGuard>,
11329    right: &HashSet<PreprocessorGuard>,
11330) -> Option<HashSet<PreprocessorGuard>> {
11331    let mut merged = left.clone();
11332    for guard in right {
11333        let boolean_negation = guard
11334            .as_boolean_expression()
11335            .map(|expression| expression.negated());
11336        if merged.contains(&guard.negated())
11337            || boolean_negation.is_some_and(|negated| {
11338                merged
11339                    .iter()
11340                    .filter_map(PreprocessorGuard::as_boolean_expression)
11341                    .any(|existing| existing == negated)
11342            })
11343        {
11344            return None;
11345        }
11346        merged.insert(guard.clone());
11347    }
11348    Some(merged)
11349}
11350
11351fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11352    if conditional.kind() == "preproc_ifdef" {
11353        let name = conditional.child_by_field_name("name")?;
11354        let name = node_text(name, source).to_string();
11355        return match conditional.child(0)?.kind() {
11356            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11357            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11358            _ => None,
11359        };
11360    }
11361    let condition = conditional.child_by_field_name("condition")?;
11362    simple_preprocessor_expression_guard(condition, source).or_else(|| {
11363        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
11364            node_text(condition, source),
11365        )))
11366    })
11367}
11368
11369fn simple_preprocessor_expression_guard(
11370    expression: Node<'_>,
11371    source: &str,
11372) -> Option<PreprocessorGuard> {
11373    match expression.kind() {
11374        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
11375            node_text(expression, source).to_string(),
11376        ))),
11377        "number_literal" => match node_text(expression, source).trim() {
11378            "0" => Some(PreprocessorGuard::Constant(false)),
11379            "1" => Some(PreprocessorGuard::Constant(true)),
11380            _ => None,
11381        },
11382        "preproc_defined" => {
11383            let identifier = (0..expression.named_child_count())
11384                .filter_map(|index| expression.named_child(index))
11385                .find(|child| child.kind() == "identifier")?;
11386            Some(PreprocessorGuard::Defined(
11387                node_text(identifier, source).to_string(),
11388            ))
11389        }
11390        "unary_expression"
11391            if expression
11392                .child_by_field_name("operator")
11393                .is_some_and(|operator| operator.kind() == "!") =>
11394        {
11395            simple_preprocessor_expression_guard(
11396                expression.child_by_field_name("argument")?,
11397                source,
11398            )
11399            .map(|guard| guard.negated())
11400        }
11401        "parenthesized_expression" => (0..expression.named_child_count())
11402            .filter_map(|index| expression.named_child(index))
11403            .next()
11404            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
11405        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
11406            expression, source,
11407        ))),
11408        _ => None,
11409    }
11410}
11411
11412fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
11413    match expression.kind() {
11414        "number_literal" => match node_text(expression, source).trim() {
11415            "0" => BooleanGuardExpression::Constant(false),
11416            "1" => BooleanGuardExpression::Constant(true),
11417            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11418                expression, source,
11419            ))),
11420        },
11421        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
11422        "preproc_defined" => {
11423            let identifier = (0..expression.named_child_count())
11424                .filter_map(|index| expression.named_child(index))
11425                .find(|child| child.kind() == "identifier");
11426            identifier.map_or_else(
11427                || {
11428                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11429                        expression, source,
11430                    )))
11431                },
11432                |identifier| {
11433                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
11434                },
11435            )
11436        }
11437        "unary_expression"
11438            if expression
11439                .child_by_field_name("operator")
11440                .is_some_and(|operator| operator.kind() == "!") =>
11441        {
11442            expression.child_by_field_name("argument").map_or_else(
11443                || {
11444                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11445                        expression, source,
11446                    )))
11447                },
11448                |argument| boolean_preprocessor_expression(argument, source).negated(),
11449            )
11450        }
11451        "parenthesized_expression" => (0..expression.named_child_count())
11452            .filter_map(|index| expression.named_child(index))
11453            .next()
11454            .map_or_else(
11455                || {
11456                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11457                        expression, source,
11458                    )))
11459                },
11460                |child| boolean_preprocessor_expression(child, source),
11461            ),
11462        "binary_expression" => {
11463            let operands = || {
11464                Some((
11465                    boolean_preprocessor_expression(
11466                        expression.child_by_field_name("left")?,
11467                        source,
11468                    ),
11469                    boolean_preprocessor_expression(
11470                        expression.child_by_field_name("right")?,
11471                        source,
11472                    ),
11473                ))
11474            };
11475            match expression
11476                .child_by_field_name("operator")
11477                .map(|operator| operator.kind())
11478            {
11479                Some("&&") => operands().map_or_else(
11480                    || {
11481                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11482                            expression, source,
11483                        )))
11484                    },
11485                    |(left, right)| BooleanGuardExpression::all([left, right]),
11486                ),
11487                Some("||") => operands().map_or_else(
11488                    || {
11489                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11490                            expression, source,
11491                        )))
11492                    },
11493                    |(left, right)| BooleanGuardExpression::any([left, right]),
11494                ),
11495                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11496                    expression, source,
11497                ))),
11498            }
11499        }
11500        _ => {
11501            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
11502        }
11503    }
11504}
11505
11506fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
11507    if targets.len() == 1 {
11508        targets.pop()
11509    } else {
11510        None
11511    }
11512}
11513
11514/// The declaration nodes of `candidate` in `prepared` that stand at a scope a
11515/// later reference can name.
11516///
11517/// A declaration inside a real function body, lambda, or nested block is block
11518/// local and is dropped. A declaration inside a parser-recovery wrapper that
11519/// merely looks callable -- an export macro between `class` and its name, or a
11520/// namespace-opening macro token before `namespace x {` -- keeps class or
11521/// namespace scope and is kept.
11522fn nameable_callable_declaration_nodes<'tree>(
11523    analyzer: &CppGraphSource<'_>,
11524    prepared: &'tree PreparedSyntaxTree,
11525    candidate: &CodeUnit,
11526) -> Vec<Node<'tree>> {
11527    callable_declaration_nodes(analyzer, prepared, candidate)
11528        .into_iter()
11529        .filter(|declaration| {
11530            let mut ancestor = declaration.parent();
11531            while let Some(node) = ancestor {
11532                if node.kind() == "function_definition"
11533                    && is_recovered_declaration_scope_container(node, prepared.source())
11534                {
11535                    ancestor = node.parent();
11536                    continue;
11537                }
11538                if node.kind() == "compound_statement"
11539                    && node.parent().is_some_and(|parent| {
11540                        is_recovered_declaration_scope_container(parent, prepared.source())
11541                    })
11542                {
11543                    ancestor = node.parent().and_then(|parent| parent.parent());
11544                    continue;
11545                }
11546                if matches!(
11547                    node.kind(),
11548                    "compound_statement" | "function_definition" | "lambda_expression"
11549                ) {
11550                    return false;
11551                }
11552                ancestor = node.parent();
11553            }
11554            true
11555        })
11556        .collect()
11557}
11558
11559fn callable_declaration_nodes<'tree>(
11560    analyzer: &CppGraphSource<'_>,
11561    prepared: &'tree PreparedSyntaxTree,
11562    candidate: &CodeUnit,
11563) -> Vec<Node<'tree>> {
11564    let root = prepared.tree().root_node();
11565    analyzer
11566        .ranges(candidate)
11567        .into_iter()
11568        .filter_map(|range| {
11569            let mut declaration =
11570                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
11571            // A declaration an attribute-like macro invocation swallowed lives
11572            // inside the `ERROR` the parser left, not inside a `declaration`
11573            // node, so that envelope is where the climb stops (#2552).
11574            //
11575            // The climb can also run out of ancestors. A collapse that takes a
11576            // whole file with it -- whisper.cpp's `include/whisper.h` from its
11577            // first `WHISPER_DEPRECATED(` to the end (#3094) -- leaves no
11578            // envelope around most of what a region reparse recovers there, and
11579            // the recovered declaration's own `;` can fall outside the envelope
11580            // it does leave, so the range spans the container rather than one
11581            // node. Either way the declaration stands at the scope the climb
11582            // ended in, which is what the callers ask these nodes about.
11583            while !matches!(
11584                declaration.kind(),
11585                "declaration" | "field_declaration" | "function_definition"
11586            ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
11587                declaration,
11588                prepared.source(),
11589            ) {
11590                let Some(parent) = declaration.parent() else {
11591                    break;
11592                };
11593                declaration = parent;
11594            }
11595            Some(declaration)
11596        })
11597        .collect()
11598}
11599
11600fn real_function_definition_ancestor<'tree>(
11601    node: Node<'tree>,
11602    source: &str,
11603) -> Option<Node<'tree>> {
11604    let mut ancestor = node.parent();
11605    while let Some(node) = ancestor {
11606        if node.kind() == "function_definition"
11607            && !is_recovered_declaration_scope_container(node, source)
11608        {
11609            return Some(node);
11610        }
11611        ancestor = node.parent();
11612    }
11613    None
11614}
11615
11616fn callable_declaration_activation_in_file(
11617    analyzer: &CppGraphSource<'_>,
11618    prepared: &PreparedSyntaxTree,
11619    candidate: &CodeUnit,
11620    reference: &CallableReferenceContext<'_>,
11621) -> Option<usize> {
11622    nameable_callable_declaration_nodes(analyzer, prepared, candidate)
11623        .into_iter()
11624        .filter(|declaration| {
11625            callable_preprocessor_context_is_visible_for_reference(
11626                *declaration,
11627                prepared.source(),
11628                reference,
11629            )
11630        })
11631        .map(callable_declaration_activation_byte)
11632        .min()
11633}
11634
11635/// C and C++ activate a declared name at the end of its declarator, not at the
11636/// end of the whole declaration. A function definition ends at the closing
11637/// brace of its body, so the declaration end byte would hide the function from
11638/// its own body and make self recursion unresolvable without a prototype.
11639fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
11640    if declaration.kind() != "function_definition" {
11641        return declaration.end_byte();
11642    }
11643    declaration
11644        .child_by_field_name("declarator")
11645        .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
11646}
11647
11648/// The reference side of a callable visibility question.
11649///
11650/// An include-graph walk and a whole-file arity activation ask the question
11651/// without one reference position, so they carry no `position` and therefore no
11652/// guard environment.
11653struct CallableReferenceContext<'a> {
11654    file: &'a ProjectFile,
11655    position: Option<CallableReferencePosition<'a>>,
11656}
11657
11658/// One reference position plus its preprocessor guard environment. The
11659/// environment is computed on demand because most declarations carry no
11660/// non-trivial guard.
11661struct CallableReferencePosition<'a> {
11662    prepared: &'a PreparedSyntaxTree,
11663    byte: usize,
11664    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
11665}
11666
11667impl CallableReferenceContext<'_> {
11668    fn is_c(&self) -> bool {
11669        self.file
11670            .rel_path()
11671            .extension()
11672            .and_then(|extension| extension.to_str())
11673            == Some("c")
11674    }
11675
11676    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
11677        let position = self.position.as_ref()?;
11678        position
11679            .guards
11680            .get_or_init(|| {
11681                position
11682                    .prepared
11683                    .tree()
11684                    .root_node()
11685                    .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
11686                    .and_then(|node| {
11687                        preprocessor_guard_environment(node, position.prepared.source())
11688                    })
11689            })
11690            .as_ref()
11691    }
11692}
11693
11694/// The undecidable preprocessor guards `node` stands under, or `None` when its
11695/// conditional context excludes it from `reference`'s translation unit outright:
11696/// a false constant, a condition this analyzer cannot read, or a `__cplusplus`
11697/// branch the reference's language never takes.
11698///
11699/// Collecting one guard per enclosing conditional makes the whole walk a
11700/// conjunction of the declaration's requirements. Which comparison those
11701/// requirements then face depends on where the declaration lives: a same-file
11702/// declaration shares the reference's configuration, so the reference's active
11703/// guards must imply them, while a foreign file resolves its own conditionals
11704/// and only has to stay compatible with the reference.
11705fn callable_declaration_guard_requirements(
11706    node: Node<'_>,
11707    source: &str,
11708    reference: &CallableReferenceContext<'_>,
11709) -> Option<HashSet<PreprocessorGuard>> {
11710    let reference_is_c = reference.is_c();
11711    let mut required = HashSet::default();
11712    let mut ancestor = node.parent();
11713    while let Some(conditional) = ancestor {
11714        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11715            && !is_file_covering_include_guard(conditional, source)
11716            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11717            && preprocessor_conditional_contains_descendant(conditional, node)
11718        {
11719            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11720            match guard {
11721                PreprocessorGuard::Constant(true) => {}
11722                PreprocessorGuard::Constant(false) => return None,
11723                PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
11724                    if reference_is_c {
11725                        return None;
11726                    }
11727                }
11728                PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
11729                    if !reference_is_c {
11730                        return None;
11731                    }
11732                }
11733                guard => {
11734                    required.insert(guard);
11735                }
11736            }
11737        }
11738        ancestor = conditional.parent();
11739    }
11740    Some(required)
11741}
11742
11743/// Whether a declaration in the reference's own file is co-active with the
11744/// reference: one translation unit resolves every conditional the same way, so
11745/// the reference's active guards must imply the declaration's requirements.
11746fn callable_preprocessor_context_is_visible_for_reference(
11747    node: Node<'_>,
11748    source: &str,
11749    reference: &CallableReferenceContext<'_>,
11750) -> bool {
11751    let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
11752        return false;
11753    };
11754    required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards())
11755}
11756
11757fn flattened_macro_namespace_declaration_matches(
11758    analyzer: &CppGraphSource<'_>,
11759    cpp: &dyn CppSource,
11760    reference_file: &ProjectFile,
11761    visible_declaration: &CodeUnit,
11762    qualified_candidate: &CodeUnit,
11763    reference_byte: usize,
11764) -> bool {
11765    // Namespace-opening macros can leave tree-sitter unable to retain the
11766    // namespace owner after a later recovery point. In that shape the forward
11767    // declaration is indexed at translation-unit scope, while the definition
11768    // still has its qualified owner. Require all surviving structural evidence
11769    // before treating the declaration as activation for that definition.
11770    if visible_declaration.kind() != qualified_candidate.kind()
11771        || visible_declaration.identifier() != qualified_candidate.identifier()
11772        || visible_declaration.signature() != qualified_candidate.signature()
11773        || !visible_declaration.package_name().is_empty()
11774        || qualified_candidate.package_name().is_empty()
11775    {
11776        return false;
11777    }
11778
11779    let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
11780        return false;
11781    };
11782    let root = prepared.tree().root_node();
11783    let closing_brace_limit = if visible_declaration.source() == reference_file {
11784        reference_byte
11785    } else {
11786        usize::MAX
11787    };
11788
11789    analyzer
11790        .ranges(visible_declaration)
11791        .into_iter()
11792        .any(|range| {
11793            let Some(mut declaration) =
11794                root.descendant_for_byte_range(range.start_byte, range.end_byte)
11795            else {
11796                return false;
11797            };
11798            while !matches!(
11799                declaration.kind(),
11800                "declaration" | "field_declaration" | "function_definition"
11801            ) {
11802                let Some(parent) = declaration.parent() else {
11803                    return false;
11804                };
11805                declaration = parent;
11806            }
11807            if declaration
11808                .parent()
11809                .is_none_or(|parent| parent.kind() != "translation_unit")
11810                || !macro_displaced_cpp_return_type(declaration, prepared.source())
11811            {
11812                return false;
11813            }
11814
11815            let mut cursor = root.walk();
11816            root.named_children(&mut cursor).any(|sibling| {
11817                sibling.start_byte() >= declaration.end_byte()
11818                    && sibling.start_byte() < closing_brace_limit
11819                    && direct_unmatched_closing_brace(sibling)
11820            })
11821        })
11822}
11823
11824fn flattened_macro_namespace_components(
11825    declaration: Node<'_>,
11826    source: &str,
11827) -> Option<Vec<String>> {
11828    flattened_macro_function_namespace_components(declaration, source)
11829        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
11830}
11831
11832fn flattened_macro_function_namespace_components(
11833    declaration: Node<'_>,
11834    source: &str,
11835) -> Option<Vec<String>> {
11836    let body = declaration
11837        .parent()
11838        .filter(|parent| parent.kind() == "compound_statement")?;
11839    let function = body.parent()?;
11840    if function.child_by_field_name("body") != Some(body) {
11841        return None;
11842    }
11843    let namespace_name = recovered_macro_namespace_name(function, source)?;
11844    let mut components = enclosing_namespace_components(declaration, source)?;
11845    components.push(namespace_name);
11846    Some(components)
11847}
11848
11849/// The namespace name a namespace-opening macro token displaced into a
11850/// synthetic `function_definition`, or `None` when `function` is not that
11851/// recovery shape.
11852///
11853/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
11854/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
11855/// the macro token, whose declarator is the namespace name behind an `ERROR`
11856/// holding the `namespace` keyword, and whose body spans the whole namespace
11857/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
11858/// artifact from a real function definition.
11859fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
11860    if function.kind() != "function_definition" || !function.has_error() {
11861        return None;
11862    }
11863    let body = function
11864        .child_by_field_name("body")
11865        .filter(|body| body.kind() == "compound_statement")?;
11866    let mut cursor = function.walk();
11867    let prefix = function
11868        .named_children(&mut cursor)
11869        .take_while(|child| child.start_byte() < body.start_byte())
11870        .filter(|child| child.kind() != "comment")
11871        .collect::<Vec<_>>();
11872    let begin_index = prefix.iter().rposition(|child| {
11873        flattened_macro_sentinel_name(*child, source)
11874            .is_some_and(|name| is_namespace_begin_sentinel(&name))
11875    })?;
11876    let mut identifiers = Vec::new();
11877    let mut stack = prefix[begin_index + 1..]
11878        .iter()
11879        .rev()
11880        .copied()
11881        .collect::<Vec<_>>();
11882    while let Some(current) = stack.pop() {
11883        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
11884            identifiers.push(identifier);
11885            continue;
11886        }
11887        let mut cursor = current.walk();
11888        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
11889        stack.extend(children.into_iter().rev());
11890    }
11891    let [keyword, namespace_name] = identifiers.as_slice() else {
11892        return None;
11893    };
11894    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
11895    {
11896        return None;
11897    }
11898    let mut next = function.next_named_sibling();
11899    let next = loop {
11900        let candidate = next?;
11901        next = candidate.next_named_sibling();
11902        if candidate.kind() != "comment" {
11903            break candidate;
11904        }
11905    };
11906    flattened_macro_sentinel_name(next, source)
11907        .is_some_and(|name| is_namespace_end_sentinel(&name))
11908        .then(|| namespace_name.clone())
11909}
11910
11911/// A `function_definition` that exists only because tree-sitter recovered a
11912/// macro-decorated class head or a namespace-opening macro token. A declaration
11913/// in such a body keeps class or namespace scope, so a scope walk must step over
11914/// the wrapper instead of treating the declaration as block local.
11915fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
11916    crate::declarations::is_recovered_exported_class_container(node, source)
11917        || crate::declarations::is_recovered_fragmented_partial_specialization_container(
11918            node, source,
11919        )
11920        || recovered_macro_namespace_name(node, source).is_some()
11921}
11922
11923fn flattened_macro_error_namespace_components(
11924    declaration: Node<'_>,
11925    source: &str,
11926) -> Option<Vec<String>> {
11927    let parent = declaration
11928        .parent()
11929        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
11930    let mut cursor = parent.walk();
11931    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
11932    let declaration_index = siblings
11933        .iter()
11934        .position(|candidate| same_node(*candidate, declaration))?;
11935    let begin_index = (0..declaration_index).rev().find(|index| {
11936        flattened_macro_sentinel_name(siblings[*index], source)
11937            .is_some_and(|name| is_namespace_begin_sentinel(&name))
11938    })?;
11939
11940    let significant = siblings[begin_index + 1..declaration_index]
11941        .iter()
11942        .copied()
11943        .filter(|node| node.kind() != "comment")
11944        .collect::<Vec<_>>();
11945    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
11946        return None;
11947    };
11948    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
11949        return None;
11950    }
11951    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
11952    if significant[2..].iter().any(|node| {
11953        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
11954            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
11955        })
11956    }) {
11957        return None;
11958    }
11959
11960    let mut saw_namespace_close = false;
11961    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
11962        if sibling.kind() == "comment" {
11963            continue;
11964        }
11965        if !saw_namespace_close {
11966            if direct_unmatched_closing_brace(sibling) {
11967                saw_namespace_close = true;
11968                continue;
11969            }
11970            if flattened_macro_sentinel_name(sibling, source).is_some() {
11971                return None;
11972            }
11973            continue;
11974        }
11975        if !flattened_macro_sentinel_name(sibling, source)
11976            .is_some_and(|name| is_namespace_end_sentinel(&name))
11977        {
11978            return None;
11979        }
11980        let mut components = enclosing_namespace_components(declaration, source)?;
11981        components.push(namespace_name);
11982        return Some(components);
11983    }
11984    None
11985}
11986
11987fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
11988    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
11989    // an `expression_statement` with a missing semicolon; inside a namespace
11990    // body the same token stays a bare `type_identifier`.
11991    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
11992        node.named_child(0)?
11993    } else {
11994        node
11995    };
11996    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
11997        node.child_by_field_name("type")
11998            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
11999    })?;
12000    (cpp_export_macro_token(&candidate)
12001        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
12002    .then_some(candidate)
12003}
12004
12005/// Namespace-opening macros are spelled both ways in the wild:
12006/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
12007fn is_namespace_begin_sentinel(name: &str) -> bool {
12008    name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
12009}
12010
12011fn is_namespace_end_sentinel(name: &str) -> bool {
12012    name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
12013}
12014
12015fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12016    if node.kind() != "ERROR" || node.named_child_count() != 1 {
12017        return None;
12018    }
12019    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12020    (!cpp_export_macro_token(&name)).then_some(name)
12021}
12022
12023fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12024    if !matches!(
12025        node.kind(),
12026        "identifier" | "namespace_identifier" | "type_identifier"
12027    ) {
12028        return None;
12029    }
12030    let name = normalize_cpp_whitespace(node_text(node, source));
12031    (!name.is_empty()).then_some(name)
12032}
12033
12034fn guard_requirement_sets_match(
12035    left: &[(usize, HashSet<PreprocessorGuard>)],
12036    right: &[(usize, HashSet<PreprocessorGuard>)],
12037) -> bool {
12038    left.len() == right.len()
12039        && left.iter().all(|(_, left_guards)| {
12040            right
12041                .iter()
12042                .any(|(_, right_guards)| left_guards == right_guards)
12043        })
12044        && right.iter().all(|(_, right_guards)| {
12045            left.iter()
12046                .any(|(_, left_guards)| right_guards == left_guards)
12047        })
12048}
12049
12050fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
12051    let Some(type_node) = declaration.child_by_field_name("type") else {
12052        return false;
12053    };
12054    let type_name = normalize_cpp_whitespace(node_text(type_node, source));
12055    !type_name.is_empty()
12056        && type_name
12057            .chars()
12058            .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
12059        && (0..declaration.named_child_count()).any(|index| {
12060            declaration
12061                .named_child(index)
12062                .is_some_and(|child| child.kind() == "ERROR")
12063        })
12064}
12065
12066fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12067    node.kind() == "ERROR"
12068        && (0..node.child_count())
12069            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12070}
12071
12072pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12073    let mut ancestor = node.parent();
12074    while let Some(parent) = ancestor {
12075        if is_preprocessor_conditional(parent)
12076            && !is_file_covering_include_guard(parent, source)
12077            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12078        {
12079            return false;
12080        }
12081        ancestor = parent.parent();
12082    }
12083    true
12084}
12085
12086fn is_split_cpp_language_linkage_wrapper(
12087    conditional: Node<'_>,
12088    descendant: Node<'_>,
12089    source: &str,
12090) -> bool {
12091    if conditional.child_by_field_name("alternative").is_some()
12092        || !matches!(
12093            simple_preprocessor_guard(conditional, source),
12094            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12095        )
12096    {
12097        return false;
12098    }
12099    let mut current = descendant.parent();
12100    let linkage = loop {
12101        let Some(node) = current else {
12102            return false;
12103        };
12104        if node == conditional {
12105            return false;
12106        }
12107        if node.kind() == "linkage_specification" {
12108            break node;
12109        }
12110        current = node.parent();
12111    };
12112    if linkage
12113        .child_by_field_name("value")
12114        .is_none_or(|value| node_text(value, source) != "\"C\"")
12115    {
12116        return false;
12117    }
12118    let Some(body) = linkage.child_by_field_name("body") else {
12119        return false;
12120    };
12121    let closes_opening_branch = (0..body.named_child_count())
12122        .filter_map(|index| body.named_child(index))
12123        .take_while(|child| child.end_byte() <= descendant.start_byte())
12124        .any(|child| {
12125            child.kind() == "preproc_call"
12126                && child
12127                    .child_by_field_name("directive")
12128                    .is_some_and(|directive| node_text(directive, source) == "#endif")
12129        });
12130    let reopens_for_closing_brace = (0..body.named_child_count())
12131        .filter_map(|index| body.named_child(index))
12132        .skip_while(|child| child.start_byte() < descendant.end_byte())
12133        .any(|child| {
12134            matches!(
12135                simple_preprocessor_guard(child, source),
12136                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12137            ) && (0..child.child_count()).any(|index| {
12138                child
12139                    .child(index)
12140                    .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
12141            })
12142        });
12143    closes_opening_branch && reopens_for_closing_brace
12144}
12145
12146/// The argument list a call-shaped node supplies: `f(args)`, `new T(args)`,
12147/// `T{args}` and the member initializer `: field(args)`, whose grammar gives its
12148/// argument list no field name.
12149pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12150    node.child_by_field_name("arguments")
12151        .or_else(|| node.child_by_field_name("parameters"))
12152        .or_else(|| node.child_by_field_name("value"))
12153        .or_else(|| first_named_child_of_kind(node, "argument_list"))
12154        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12155}
12156
12157pub fn call_arity(node: Node<'_>) -> usize {
12158    call_arguments_node(node)
12159        .map(|args| argument_children(args).count())
12160        .unwrap_or(0)
12161}
12162
12163pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12164    let recovered_block_arguments = recovered_block_literal_arguments(node);
12165    (0..node.child_count())
12166        .filter_map(move |index| node.child(index))
12167        .filter(|child| child.is_named() && !child.is_extra())
12168        .flat_map(move |child| {
12169            if let Some((raw, left, right)) = recovered_block_arguments
12170                && child == raw
12171            {
12172                [Some(left), Some(right)]
12173            } else {
12174                [Some(child), None]
12175            }
12176        })
12177        .flatten()
12178}
12179
12180/// Recover the two ordinary C values that the C++ grammar folds into one
12181/// `new_expression` for `callee(new, trailing)`.
12182///
12183/// The malformed node has an exact grammar-owned shape: the anonymous `new`
12184/// token, an extra `ERROR` containing only the comma token, and the trailing
12185/// value in the `type` field.  The caller supplies the compilation-dialect
12186/// proof from [`reference_uses_c_semantics`]; a C++ source therefore never
12187/// reinterprets a real new-expression through this path.
12188pub fn recovered_c_new_expression_arguments(
12189    node: Node<'_>,
12190    uses_c_semantics: bool,
12191) -> Option<[Node<'_>; 2]> {
12192    if !uses_c_semantics || node.kind() != "new_expression" {
12193        return None;
12194    }
12195    let parent = node.parent()?;
12196    if parent.kind() != "argument_list" {
12197        return None;
12198    }
12199    let keyword = node.child(0)?;
12200    let error = node.child(1)?;
12201    let trailing = node.child(2)?;
12202    if node.child(3).is_some()
12203        || keyword.kind() != "new"
12204        || keyword.is_named()
12205        || keyword.child_count() != 0
12206        || error.kind() != "ERROR"
12207        || !error.is_extra()
12208        || error.child_count() != 1
12209        || error.child(0).is_none_or(|comma| comma.kind() != ",")
12210        || node.child_by_field_name("type") != Some(trailing)
12211        || trailing.kind() != "type_identifier"
12212    {
12213        return None;
12214    }
12215    Some([keyword, trailing])
12216}
12217
12218/// The recovered C value covering one focused source range, starting from any
12219/// node within the malformed new-expression.
12220pub fn recovered_c_new_expression_argument_at(
12221    mut node: Node<'_>,
12222    start_byte: usize,
12223    end_byte: usize,
12224    uses_c_semantics: bool,
12225) -> Option<Node<'_>> {
12226    // The recovery only exists for C sources, and the climb below reaches the
12227    // root before it can answer no. `Node::parent` re-descends from the root on
12228    // every step, so leaving the check inside the loop made every scanned node
12229    // of a C++ file pay a full quadratic ancestor walk for an answer settled by
12230    // the file's language (#3097).
12231    if !uses_c_semantics {
12232        return None;
12233    }
12234    loop {
12235        if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12236            return arguments.into_iter().find(|argument| {
12237                argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12238            });
12239        }
12240        node = node.parent()?;
12241    }
12242}
12243
12244fn recovered_c_keyword_argument_count(
12245    file: &ProjectFile,
12246    call: Node<'_>,
12247    arguments: Node<'_>,
12248    source: &str,
12249) -> usize {
12250    // A C identifier that is a C++ keyword can be displaced twice by the C++
12251    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
12252    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
12253    // the enclosing C function before restoring the otherwise dropped slot.
12254    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12255        return 0;
12256    }
12257    let mut ancestor = Some(call);
12258    let function = loop {
12259        let Some(current) = ancestor else {
12260            return 0;
12261        };
12262        if current.kind() == "function_definition" {
12263            break current;
12264        }
12265        ancestor = current.parent();
12266    };
12267    let Some(parameters) = function
12268        .child_by_field_name("declarator")
12269        .and_then(|declarator| declarator.child_by_field_name("parameters"))
12270    else {
12271        return 0;
12272    };
12273    let displaced_parameter_keywords = (0..parameters.child_count())
12274        .filter_map(|index| parameters.child(index))
12275        .filter(|error| error.kind() == "ERROR")
12276        .filter_map(|error| {
12277            let parameter = error.prev_named_sibling()?;
12278            if parameter.kind() != "parameter_declaration"
12279                || parameter.end_byte() != error.start_byte()
12280                || extract_variable_name(parameter, source).is_some()
12281            {
12282                return None;
12283            }
12284            let mut children = (0..error.child_count())
12285                .filter_map(|index| error.child(index))
12286                .filter(|child| !child.is_extra() && !child.is_missing());
12287            let keyword = children.next()?;
12288            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12289                .then_some(keyword)
12290        })
12291        .collect::<Vec<_>>();
12292    if displaced_parameter_keywords.is_empty() {
12293        return 0;
12294    }
12295
12296    (0..arguments.child_count())
12297        .filter_map(|index| arguments.child(index))
12298        .filter(|error| error.kind() == "ERROR" && error.is_extra())
12299        .filter(|error| {
12300            let mut children = (0..error.child_count())
12301                .filter_map(|index| error.child(index))
12302                .filter(|child| !child.is_extra() && !child.is_missing());
12303            let Some(comma) = children.next() else {
12304                return false;
12305            };
12306            let Some(keyword) = children.next() else {
12307                return false;
12308            };
12309            children.next().is_none()
12310                && comma.kind() == ","
12311                && !keyword.is_named()
12312                && keyword.child_count() == 0
12313                && displaced_parameter_keywords
12314                    .iter()
12315                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
12316        })
12317        .count()
12318}
12319
12320fn recovered_block_literal_arguments<'tree>(
12321    arguments: Node<'tree>,
12322) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
12323    if arguments.kind() != "argument_list" {
12324        return None;
12325    }
12326    let mut raw_arguments = (0..arguments.child_count())
12327        .filter_map(|index| arguments.child(index))
12328        .filter(|child| child.is_named() && !child.is_extra());
12329    let raw = raw_arguments.next()?;
12330    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
12331        return None;
12332    }
12333
12334    let left = raw.child_by_field_name("left")?;
12335    if left.is_missing() || left.start_byte() == left.end_byte() {
12336        return None;
12337    }
12338    let right = raw.child_by_field_name("right")?;
12339    if right.kind() != "compound_literal_expression"
12340        || right.is_missing()
12341        || right
12342            .child_by_field_name("type")
12343            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
12344        || right
12345            .child_by_field_name("value")
12346            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
12347    {
12348        return None;
12349    }
12350    let has_intervening_error = (0..raw.child_count())
12351        .filter_map(|index| raw.child(index))
12352        .any(|child| {
12353            child.kind() == "ERROR"
12354                && !child.is_missing()
12355                && child.start_byte() >= left.end_byte()
12356                && child.end_byte() <= right.start_byte()
12357        });
12358    has_intervening_error.then_some((raw, left, right))
12359}
12360
12361pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
12362    match node.kind() {
12363        "new_expression" => node
12364            .child_by_field_name("type")
12365            .or_else(|| node.named_child(0)),
12366        "compound_literal_expression" => node.child_by_field_name("type"),
12367        "call_expression" => node.child_by_field_name("function"),
12368        _ => None,
12369    }
12370}
12371
12372/// The structured type named by a C-style cast expression.
12373///
12374/// Tree-sitter wraps the actual type syntax in a `type_descriptor`.  Return its
12375/// structured `type` field so resolution retains qualified and nested syntax
12376/// without reparsing source text.
12377pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
12378    if node.kind() != "cast_expression" {
12379        return None;
12380    }
12381    let descriptor = node.child_by_field_name("type")?;
12382    if descriptor.kind() == "type_descriptor" {
12383        descriptor.child_by_field_name("type")
12384    } else {
12385        Some(descriptor)
12386    }
12387}
12388
12389/// The expression a field access reads its member from.
12390///
12391/// That is normally the node in the `argument` field. A preprocessor
12392/// conditional inside a call's argument list defeats the grammar: tree-sitter
12393/// ends the argument list at the `#if` line, then reads the text that follows
12394/// `#endif` as a field access on the whole call expression and leaves the real
12395/// receiver in an `ERROR` node between the `argument` child and the operator.
12396/// mbedtls writes exactly that at `library/ssl_tls.c:4999`, where
12397/// `ssl->session->encrypt_then_mac` is one conditionally compiled call argument
12398/// (#2982).
12399///
12400/// `.` and `->` read whatever ends immediately before them in either tree, so
12401/// answer from the operator's position instead of trusting the `argument`
12402/// field, and unwrap the single expression a displaced `ERROR` holds.
12403pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
12404    debug_assert_eq!(field.kind(), "field_expression");
12405    let operator = field.child_by_field_name("operator")?;
12406    let mut cursor = field.walk();
12407    let receiver = field
12408        .named_children(&mut cursor)
12409        .filter(|child| child.end_byte() <= operator.start_byte())
12410        .last()?;
12411    if receiver.kind() != "ERROR" {
12412        return Some(receiver);
12413    }
12414    (receiver.named_child_count() == 1)
12415        .then(|| receiver.named_child(0))
12416        .flatten()
12417}
12418
12419pub fn field_initializer_constructs_target(
12420    node: Node<'_>,
12421    ctx: &ScanCtx<'_>,
12422    owner: &CodeUnit,
12423) -> bool {
12424    // A qualified name in a constructor initializer denotes a base
12425    // subobject constructor (`namespace::Base(args)`), not a member field.  The
12426    // field-initializer grammar exposes the qualified name as one structured
12427    // `qualified_identifier`; resolve its owner through the same lexical type
12428    // machinery used for ordinary C++ type references before considering the
12429    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
12430    // qualified non-constructor member, and an unresolved owner out of the
12431    // target constructor's inverse usage set.
12432    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
12433        return qualified_base_initializer_constructs_target(node, ctx, owner);
12434    }
12435    let Some(name) = node
12436        .child_by_field_name("name")
12437        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
12438        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
12439    else {
12440        return false;
12441    };
12442    let field_name = node_text(name, ctx.source);
12443    ctx.visibility
12444        .visible_identifier_candidates(ctx.file, field_name)
12445        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
12446        .any(|unit| field_declares_type(unit, ctx, owner))
12447}
12448
12449fn qualified_base_initializer_constructs_target(
12450    node: Node<'_>,
12451    ctx: &ScanCtx<'_>,
12452    owner: &CodeUnit,
12453) -> bool {
12454    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
12455        return false;
12456    };
12457    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
12458        return false;
12459    };
12460    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
12461        return false;
12462    };
12463    let resolves_target = |components: &[String]| {
12464        matches!(
12465            ctx.visibility.resolve_type_components_lexically_for_target(
12466                &ctx.analyzer,
12467                ctx.file,
12468                components,
12469                is_globally_qualified_cpp_name(qualified),
12470                &lexical_scope,
12471                owner,
12472            ),
12473            LexicalTypeResolution::Resolved { unit, .. }
12474                if same_visible_symbol(&unit, owner)
12475        )
12476    };
12477    if resolves_target(&components) {
12478        return true;
12479    }
12480
12481    // Some real-world code spells a base mem-initializer as
12482    // `Base::Base(args)`. In that structured path the final component repeats
12483    // the constructor name; resolve the preceding type path. The terminal
12484    // identity check prevents an arbitrary qualified member from taking this
12485    // route.
12486    components
12487        .last()
12488        .is_some_and(|terminal| terminal == owner.identifier())
12489        && resolves_target(&components[..components.len() - 1])
12490}
12491
12492fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
12493    unit.signature()
12494        .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
12495        || ctx
12496            .analyzer
12497            .get_source(unit, false)
12498            .is_some_and(|declaration| {
12499                field_declaration_type_matches(&declaration, unit, ctx, owner)
12500            })
12501}
12502
12503pub fn field_declared_binding(
12504    analyzer: &CppGraphSource<'_>,
12505    visibility: &VisibilityIndex<'_>,
12506    visible_from: &ProjectFile,
12507    field: &CodeUnit,
12508) -> Option<CppScanBinding> {
12509    let fact = visibility.field_declared_type_fact(analyzer, field)?;
12510    let normalized = normalize_field_type_text(&fact.type_text);
12511    let resolved = visibility.resolve_unique_canonical_type_for_declaration(
12512        analyzer,
12513        visible_from,
12514        field,
12515        &normalized,
12516    );
12517    let resolved = match (resolved, fact.template_arguments.as_deref()) {
12518        (Some(primary), Some(arguments)) => visibility
12519            .resolve_template_arguments(visible_from, primary, arguments)
12520            .ok(),
12521        (resolved, None) => resolved,
12522        (None, Some(_)) => None,
12523    }
12524    .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
12525    Some(CppScanBinding::from_type_name(
12526        normalized,
12527        resolved,
12528        fact.indirection,
12529    ))
12530}
12531
12532/// Resolve the receiver type minted for a named declarator on an anonymous C
12533/// aggregate, such as `struct { int r; } c`. The declaration index preserves
12534/// the aggregate as the nested class `Owner$c`, but the field's type fact has
12535/// no spelling that can name that class. Confirm the anonymous aggregate from
12536/// its parsed declaration, then use the owner's structured child relationship
12537/// to recover the one corresponding receiver type.
12538fn anonymous_aggregate_field_owner(
12539    analyzer: &CppGraphSource<'_>,
12540    visibility: &VisibilityIndex<'_>,
12541    visible_from: &ProjectFile,
12542    field: &CodeUnit,
12543) -> Option<CodeUnit> {
12544    let owner = type_owner_of(analyzer, field)?;
12545    if !owner.is_class() {
12546        return None;
12547    }
12548    let declaration = analyzer.get_source(field, false)?;
12549    let mut parser = Parser::new();
12550    parser
12551        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12552        .ok()?;
12553    let tree = parser.parse(&declaration, None)?;
12554    let mut stack = vec![tree.root_node()];
12555    while let Some(node) = stack.pop() {
12556        if matches!(node.kind(), "declaration" | "field_declaration")
12557            && let Some(type_node) = node
12558                .child_by_field_name("type")
12559                .or_else(|| first_type_child(node))
12560            && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
12561            && type_node.child_by_field_name("name").is_none()
12562            && declared_name_indirection(node, type_node, field.identifier(), &declaration)
12563                .is_some()
12564        {
12565            let matches = visibility
12566                .visible_members_for_owner_name(visible_from, &owner, field.identifier())
12567                .into_iter()
12568                .filter(|child| child.is_class() && child.identifier() == field.identifier())
12569                .collect::<Vec<_>>();
12570            return match matches.as_slice() {
12571                [child] => Some((*child).clone()),
12572                _ => None,
12573            };
12574        }
12575        let mut cursor = node.walk();
12576        stack.extend(node.named_children(&mut cursor));
12577    }
12578    None
12579}
12580
12581/// Resolve an anonymous C aggregate's generated owner from its declaration
12582/// range. Anonymous local structs and unions have no type name to enter into
12583/// the visibility index; declaration extraction gives them a structured class
12584/// identity keyed by the aggregate node's exact CST range instead. Matching
12585/// that range keeps nested aggregates and unrelated generated owners out of
12586/// the result without inspecting source text.
12587pub fn anonymous_aggregate_owner(
12588    analyzer: &CppGraphSource<'_>,
12589    file: &ProjectFile,
12590    node: Node<'_>,
12591) -> Option<CodeUnit> {
12592    if !matches!(node.kind(), "struct_specifier" | "union_specifier")
12593        || node.child_by_field_name("name").is_some()
12594    {
12595        return None;
12596    }
12597    let mut candidates = analyzer
12598        .declarations(file)
12599        .into_iter()
12600        .filter(|candidate| {
12601            candidate.is_class()
12602                && analyzer.ranges(candidate).into_iter().any(|range| {
12603                    range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
12604                })
12605        })
12606        .collect::<Vec<_>>();
12607    candidates.sort_by_key(|candidate| candidate.fq_name());
12608    candidates.dedup();
12609    match candidates.as_slice() {
12610        [candidate] => Some(candidate.clone()),
12611        _ => None,
12612    }
12613}
12614
12615/// The one logical type the candidates name, or why they do not name one.
12616fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
12617    let Some(first) = candidates.first() else {
12618        return Err(TypeCandidateFailure::Unresolvable);
12619    };
12620    if candidates
12621        .iter()
12622        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
12623    {
12624        Ok((*first).clone())
12625    } else {
12626        Err(TypeCandidateFailure::Ambiguous)
12627    }
12628}
12629
12630fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
12631    logical_type_candidate(candidates).ok()
12632}
12633
12634fn unique_type_candidate_preserving_alias(
12635    analyzer: &CppGraphSource<'_>,
12636    file: &ProjectFile,
12637    candidates: &[&CodeUnit],
12638) -> Option<CodeUnit> {
12639    let first = *candidates.first()?;
12640    if declared_type_alias(analyzer, first) {
12641        return candidates
12642            .iter()
12643            .all(|candidate| {
12644                declared_type_alias(analyzer, candidate)
12645                    && candidate.kind() == first.kind()
12646                    && candidate.fq_name() == first.fq_name()
12647                    && candidate.source() == first.source()
12648            })
12649            .then(|| first.clone());
12650    }
12651    if analyzer.reference_uses_c_semantics(file)
12652        && first.is_class()
12653        && indexed_c_tag_kind(analyzer, first).is_some()
12654    {
12655        let mut full_source = None;
12656        let mut tag_kind = None;
12657        for candidate in candidates.iter().copied() {
12658            let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
12659            if tag_kind
12660                .replace(candidate_tag_kind)
12661                .is_some_and(|existing| existing != candidate_tag_kind)
12662            {
12663                return None;
12664            }
12665            if cpp_class_declaration_strength(analyzer, candidate)
12666                == CppClassDeclarationStrength::Full
12667                && full_source
12668                    .replace(candidate.source())
12669                    .is_some_and(|existing| existing != candidate.source())
12670            {
12671                return None;
12672            }
12673        }
12674    }
12675    candidates
12676        .iter()
12677        .all(|candidate| {
12678            !declared_type_alias(analyzer, candidate)
12679                && candidate.kind() == first.kind()
12680                && candidate.fq_name() == first.fq_name()
12681        })
12682        .then(|| first.clone())
12683}
12684
12685fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
12686    is_type_alias(unit)
12687        || analyzer
12688            .type_alias_provider()
12689            .is_some_and(|provider| provider.is_type_alias(unit))
12690}
12691
12692pub fn field_declared_type_binding(
12693    analyzer: &CppGraphSource<'_>,
12694    visibility: &VisibilityIndex<'_>,
12695    visible_from: &ProjectFile,
12696    field: &CodeUnit,
12697) -> Option<(String, Option<CodeUnit>, i32)> {
12698    let fact = visibility.field_declared_type_fact(analyzer, field)?;
12699    let normalized = normalize_field_type_text(&fact.type_text);
12700    let primary = visibility.resolve_unique_canonical_type_for_declaration(
12701        analyzer,
12702        visible_from,
12703        field,
12704        &normalized,
12705    );
12706    let resolved = match (primary, fact.template_arguments.as_deref()) {
12707        (Some(primary), Some(arguments)) => visibility
12708            .resolve_template_arguments(visible_from, primary, arguments)
12709            .ok(),
12710        (resolved, None) => resolved,
12711        (None, Some(_)) => None,
12712    };
12713    Some((normalized, resolved, fact.indirection))
12714}
12715
12716fn decode_field_declared_type_fact(
12717    analyzer: &CppGraphSource<'_>,
12718    field: &CodeUnit,
12719) -> Option<DeclaredFieldTypeFact> {
12720    let Some(declaration) = analyzer.get_source(field, false) else {
12721        return decode_indexed_field_declared_type_fact(analyzer, field);
12722    };
12723    let mut parser = Parser::new();
12724    parser
12725        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12726        .ok()?;
12727    // A field's indexed source is stored without its surrounding class body.
12728    // Give tree-sitter that grammatical context before checking recovery-only
12729    // field shapes such as `PyObject_HEAD Imaging image;`.
12730    let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
12731    let contextual_tree = parser.parse(&contextual_declaration, None)?;
12732    let mut stack = vec![contextual_tree.root_node()];
12733    while let Some(node) = stack.pop() {
12734        if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
12735            && node_text(recovered.name, &contextual_declaration) == field.identifier()
12736        {
12737            return Some(DeclaredFieldTypeFact {
12738                type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
12739                indirection: recovered.pointer_depth(),
12740                template_arguments: None,
12741            });
12742        }
12743        if let Some(recovered) =
12744            recovered_function_like_field_declarator(node, &contextual_declaration)
12745            && node_text(recovered.name, &contextual_declaration) == field.identifier()
12746        {
12747            let type_node = node
12748                .child_by_field_name("type")
12749                .or_else(|| first_type_child(node))?;
12750            return Some(DeclaredFieldTypeFact {
12751                type_text: node_text(type_node, &contextual_declaration).to_string(),
12752                indirection: recovered.pointer_depth(),
12753                template_arguments: cpp_template_reference_arguments(
12754                    type_node,
12755                    &contextual_declaration,
12756                ),
12757            });
12758        }
12759        if let Some(fact) =
12760            decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
12761        {
12762            return Some(fact);
12763        }
12764        let mut cursor = node.walk();
12765        stack.extend(node.named_children(&mut cursor));
12766    }
12767    let tree = parser.parse(&declaration, None)?;
12768    let mut stack = vec![tree.root_node()];
12769    while let Some(node) = stack.pop() {
12770        if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
12771        {
12772            return Some(fact);
12773        }
12774        let mut cursor = node.walk();
12775        stack.extend(node.named_children(&mut cursor));
12776    }
12777    None
12778}
12779
12780/// Decode a field whose generated owner prevents the ordinary source lookup
12781/// from selecting a standalone declaration. The indexed ranges still point
12782/// into the physical syntax tree, so recover the enclosing declaration from
12783/// that structure and apply the same declarator decoder to it.
12784fn decode_indexed_field_declared_type_fact(
12785    analyzer: &CppGraphSource<'_>,
12786    field: &CodeUnit,
12787) -> Option<DeclaredFieldTypeFact> {
12788    let cpp = analyzer.cpp?;
12789    let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
12790    let source = prepared.source();
12791    let root = prepared.tree().root_node();
12792    for range in analyzer.ranges(field) {
12793        let end = range.start_byte.saturating_add(1).min(source.len());
12794        let mut current = root.descendant_for_byte_range(range.start_byte, end);
12795        while let Some(node) = current {
12796            if matches!(node.kind(), "declaration" | "field_declaration")
12797                && let Some(fact) =
12798                    decode_declared_field_type_node(node, field.identifier(), source)
12799            {
12800                return Some(fact);
12801            }
12802            current = node.parent();
12803        }
12804    }
12805    None
12806}
12807
12808fn decode_declared_field_type_node(
12809    node: Node<'_>,
12810    field_name: &str,
12811    source: &str,
12812) -> Option<DeclaredFieldTypeFact> {
12813    if !matches!(node.kind(), "declaration" | "field_declaration") {
12814        return None;
12815    }
12816    let type_node = node
12817        .child_by_field_name("type")
12818        .or_else(|| first_type_child(node))?;
12819    let indirection = declared_name_indirection(node, type_node, field_name, source)?;
12820    let declared_type = if matches!(
12821        type_node.kind(),
12822        "class_specifier" | "struct_specifier" | "union_specifier"
12823    ) {
12824        type_node.child_by_field_name("name")
12825    } else {
12826        Some(type_node)
12827    };
12828    Some(DeclaredFieldTypeFact {
12829        type_text: declared_type.map_or_else(
12830            || field_name.to_string(),
12831            |declared_type| node_text(declared_type, source).to_string(),
12832        ),
12833        indirection,
12834        template_arguments: declared_type
12835            .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
12836    })
12837}
12838
12839/// Text of the type that a C or C++ alias declaration names, read from the
12840/// `type_definition` or `alias_declaration` node's `type` field.
12841///
12842/// The declaration text is never scanned. A function-pointer typedef
12843/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
12844/// so no prefix or suffix of the spelling isolates the target.
12845///
12846/// An alias whose declarator is a function declarator names a function type:
12847/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
12848/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
12849/// so such an alias has no canonical target. Its `type` field holds the return
12850/// type `R`, which is a different type from the alias, so this returns `None`
12851/// rather than that return type.
12852pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
12853    let mut parser = Parser::new();
12854    parser
12855        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12856        .ok()?;
12857    let tree = parser.parse(declaration, None)?;
12858    let mut stack = vec![tree.root_node()];
12859    while let Some(node) = stack.pop() {
12860        let type_node = match node.kind() {
12861            "type_definition" => {
12862                let mut cursor = node.walk();
12863                if node
12864                    .children_by_field_name("declarator", &mut cursor)
12865                    .any(declarator_names_function_type)
12866                {
12867                    return None;
12868                }
12869                node.child_by_field_name("type")?
12870            }
12871            "alias_declaration" => {
12872                let type_node = node.child_by_field_name("type")?;
12873                if type_node
12874                    .child_by_field_name("declarator")
12875                    .is_some_and(declarator_names_function_type)
12876                {
12877                    return None;
12878                }
12879                type_node
12880            }
12881            _ => {
12882                let mut cursor = node.walk();
12883                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
12884                stack.extend(children.into_iter().rev());
12885                continue;
12886            }
12887        };
12888        return Some(node_text(type_node, declaration).to_string());
12889    }
12890    None
12891}
12892
12893/// Whether an alias declaration's own declarator adds indirection that
12894/// [`cpp_alias_declaration_target_text`] does not report.
12895///
12896/// That function reads the declaration's `type` field, where `typedef Foo *Bar`
12897/// keeps only `Foo`: the `*` lives in the sibling declarator. Substituting such
12898/// an alias would equate `f(Bar)` with `f(Foo)`, so a comparison that cannot
12899/// prove the alias adds no indirection must refuse to follow it. A declaration
12900/// this cannot read at all is refused for the same reason.
12901fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
12902    let mut parser = Parser::new();
12903    if parser
12904        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12905        .is_err()
12906    {
12907        return true;
12908    }
12909    let Some(tree) = parser.parse(declaration, None) else {
12910        return true;
12911    };
12912    let mut stack = vec![tree.root_node()];
12913    while let Some(node) = stack.pop() {
12914        let declarators = match node.kind() {
12915            "type_definition" => {
12916                let mut cursor = node.walk();
12917                node.children_by_field_name("declarator", &mut cursor)
12918                    .collect::<Vec<_>>()
12919            }
12920            "alias_declaration" => node
12921                .child_by_field_name("type")
12922                .and_then(|type_node| type_node.child_by_field_name("declarator"))
12923                .into_iter()
12924                .collect::<Vec<_>>(),
12925            _ => {
12926                let mut cursor = node.walk();
12927                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
12928                stack.extend(children.into_iter().rev());
12929                continue;
12930            }
12931        };
12932        return declarators.into_iter().any(cpp_declarator_adds_indirection);
12933    }
12934    true
12935}
12936
12937/// True when an alias declarator names a function type.
12938///
12939/// The declarator chain is walked through the `declarator` field, so the
12940/// parameter list -- a sibling field -- is never entered and a parameter's own
12941/// function declarator cannot be mistaken for the alias's.
12942fn declarator_names_function_type(declarator: Node<'_>) -> bool {
12943    let mut current = Some(declarator);
12944    while let Some(node) = current {
12945        match node.kind() {
12946            "function_declarator" | "abstract_function_declarator" => return true,
12947            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
12948                current = node.named_child(0);
12949            }
12950            _ => current = node.child_by_field_name("declarator"),
12951        }
12952    }
12953    false
12954}
12955
12956/// Whether one indexed field declaration is a function or function-pointer
12957/// value. This follows tree-sitter declarator fields and never infers
12958/// callability from source spelling.
12959pub fn cpp_field_declaration_names_function_type(declaration: &str, field_name: &str) -> bool {
12960    let mut parser = Parser::new();
12961    if parser
12962        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12963        .is_err()
12964    {
12965        return false;
12966    }
12967    let Some(tree) = parser.parse(declaration, None) else {
12968        return false;
12969    };
12970    let mut stack = vec![tree.root_node()];
12971    while let Some(node) = stack.pop() {
12972        if matches!(node.kind(), "declaration" | "field_declaration") {
12973            let mut cursor = node.walk();
12974            if node
12975                .children_by_field_name("declarator", &mut cursor)
12976                .any(|declarator| {
12977                    declarator_name_node(declarator).is_some_and(|name| {
12978                        node_text(name, declaration) == field_name
12979                            && declarator_names_function_type(declarator)
12980                    })
12981                })
12982            {
12983                return true;
12984            }
12985        }
12986        let mut cursor = node.walk();
12987        stack.extend(node.named_children(&mut cursor));
12988    }
12989    false
12990}
12991
12992/// Whether one indexed alias declaration names a function or function-pointer
12993/// type. The alias name is matched through the declarator field so a function
12994/// type used by a parameter cannot be mistaken for the alias itself.
12995pub fn cpp_alias_declaration_names_function_type(declaration: &str, alias_name: &str) -> bool {
12996    let mut parser = Parser::new();
12997    if parser
12998        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12999        .is_err()
13000    {
13001        return false;
13002    }
13003    let Some(tree) = parser.parse(declaration, None) else {
13004        return false;
13005    };
13006    let mut stack = vec![tree.root_node()];
13007    while let Some(node) = stack.pop() {
13008        match node.kind() {
13009            "type_definition" => {
13010                let mut cursor = node.walk();
13011                if node
13012                    .children_by_field_name("declarator", &mut cursor)
13013                    .any(|declarator| {
13014                        extract_typedef_declarator_name(declarator, declaration)
13015                            .is_some_and(|name| name == alias_name)
13016                            && declarator_names_function_type(declarator)
13017                    })
13018                {
13019                    return true;
13020                }
13021            }
13022            "alias_declaration" => {
13023                let names_alias = node
13024                    .child_by_field_name("name")
13025                    .is_some_and(|name| node_text(name, declaration) == alias_name);
13026                if names_alias
13027                    && node
13028                        .child_by_field_name("type")
13029                        .and_then(|type_node| type_node.child_by_field_name("declarator"))
13030                        .is_some_and(declarator_names_function_type)
13031                {
13032                    return true;
13033                }
13034            }
13035            _ => {}
13036        }
13037        let mut cursor = node.walk();
13038        stack.extend(node.named_children(&mut cursor));
13039    }
13040    false
13041}
13042
13043fn decode_structured_alias_target(
13044    analyzer: &CppGraphSource<'_>,
13045    unit: &CodeUnit,
13046) -> Option<StructuredAliasTarget> {
13047    analyzer
13048        .get_source(unit, false)
13049        .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
13050        .or_else(|| {
13051            let signature = unit.signature()?;
13052            decode_structured_alias_target_source(unit, signature, false)
13053        })
13054}
13055
13056fn decode_structured_alias_target_source(
13057    unit: &CodeUnit,
13058    declaration: &str,
13059    require_top_level: bool,
13060) -> Option<StructuredAliasTarget> {
13061    let mut parser = Parser::new();
13062    parser
13063        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13064        .ok()?;
13065    let tree = parser.parse(declaration, None)?;
13066    let mut stack = vec![tree.root_node()];
13067    while let Some(node) = stack.pop() {
13068        let type_node = match node.kind() {
13069            "type_definition" => {
13070                if require_top_level
13071                    && node
13072                        .parent()
13073                        .is_none_or(|parent| parent.kind() != "translation_unit")
13074                {
13075                    let mut cursor = node.walk();
13076                    stack.extend(node.named_children(&mut cursor));
13077                    continue;
13078                }
13079                let mut declarator_cursor = node.walk();
13080                let declarator = node
13081                    .children_by_field_name("declarator", &mut declarator_cursor)
13082                    .find(|declarator| {
13083                        extract_typedef_declarator_name(*declarator, declaration)
13084                            .is_some_and(|name| name == unit.identifier())
13085                    })?;
13086                if declarator_names_function_type(declarator) {
13087                    return None;
13088                }
13089                node.child_by_field_name("type")?
13090            }
13091            "alias_declaration" => {
13092                if require_top_level
13093                    && node
13094                        .parent()
13095                        .is_none_or(|parent| parent.kind() != "translation_unit")
13096                {
13097                    let mut cursor = node.walk();
13098                    stack.extend(node.named_children(&mut cursor));
13099                    continue;
13100                }
13101                let name = node.child_by_field_name("name")?;
13102                if node_text(name, declaration) != unit.identifier() {
13103                    return None;
13104                }
13105                let type_node = node.child_by_field_name("type")?;
13106                if type_node
13107                    .child_by_field_name("declarator")
13108                    .is_some_and(declarator_names_function_type)
13109                {
13110                    return None;
13111                }
13112                type_node
13113            }
13114            _ => {
13115                let mut cursor = node.walk();
13116                stack.extend(node.named_children(&mut cursor));
13117                continue;
13118            }
13119        };
13120        return structured_alias_type_target(type_node, declaration);
13121    }
13122    None
13123}
13124
13125fn structured_alias_type_target(
13126    mut type_node: Node<'_>,
13127    source: &str,
13128) -> Option<StructuredAliasTarget> {
13129    while type_node.kind() == "type_descriptor" {
13130        type_node = type_node.child_by_field_name("type")?;
13131    }
13132    if type_node.kind() == "primitive_type" {
13133        return Some(StructuredAliasTarget::Builtin);
13134    }
13135    if matches!(
13136        type_node.kind(),
13137        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13138    ) {
13139        type_node = type_node.child_by_field_name("name")?;
13140    }
13141    let global = type_node.child_by_field_name("scope").is_none()
13142        && type_node.child(0).is_some_and(|child| child.kind() == "::");
13143    let mut components = Vec::new();
13144    append_structured_type_components(type_node, source, &mut components)?;
13145    let arguments = cpp_template_reference_arguments(type_node, source);
13146    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
13147        components,
13148        global,
13149        arguments,
13150    })
13151}
13152
13153fn append_structured_type_components(
13154    node: Node<'_>,
13155    source: &str,
13156    out: &mut Vec<String>,
13157) -> Option<()> {
13158    match node.kind() {
13159        "identifier" | "namespace_identifier" | "type_identifier" => {
13160            out.push(node_text(node, source).to_string());
13161            Some(())
13162        }
13163        "template_type" => {
13164            append_structured_type_components(node.child_by_field_name("name")?, source, out)
13165        }
13166        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13167            if let Some(scope) = node.child_by_field_name("scope") {
13168                append_structured_type_components(scope, source, out)?;
13169            }
13170            append_structured_type_components(node.child_by_field_name("name")?, source, out)
13171        }
13172        _ => None,
13173    }
13174}
13175
13176pub(crate) fn declared_name_indirection(
13177    declaration: Node<'_>,
13178    type_node: Node<'_>,
13179    field_name: &str,
13180    source: &str,
13181) -> Option<i32> {
13182    let mut stack = Vec::new();
13183    let mut cursor = declaration.walk();
13184    stack.extend(
13185        declaration
13186            .named_children(&mut cursor)
13187            .filter(|child| !same_node(*child, type_node)),
13188    );
13189    while let Some(node) = stack.pop() {
13190        if matches!(node.kind(), "identifier" | "field_identifier")
13191            && node_text(node, source) == field_name
13192        {
13193            let mut indirection = 0;
13194            let mut current = node.parent();
13195            while let Some(parent) = current {
13196                if same_node(parent, declaration) {
13197                    return Some(indirection);
13198                }
13199                if parent.kind() == "pointer_declarator" {
13200                    indirection += 1;
13201                }
13202                current = parent.parent();
13203            }
13204            return None;
13205        }
13206        let mut cursor = node.walk();
13207        stack.extend(node.named_children(&mut cursor));
13208    }
13209    None
13210}
13211
13212fn field_declaration_type_matches(
13213    declaration: &str,
13214    unit: &CodeUnit,
13215    ctx: &ScanCtx<'_>,
13216    owner: &CodeUnit,
13217) -> bool {
13218    ctx.visibility
13219        .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
13220        || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
13221            let normalized = normalize_field_type_text(type_text);
13222            ctx.visibility
13223                .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
13224                || ctx.visibility.resolves_to_type(
13225                    &ctx.analyzer,
13226                    ctx.file,
13227                    normalized.as_str(),
13228                    owner,
13229                )
13230        })
13231}
13232
13233fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
13234    let declaration = declaration
13235        .split(['=', ';'])
13236        .next()
13237        .unwrap_or(declaration)
13238        .trim();
13239    let index = declaration.rfind(field_name)?;
13240    let before = &declaration[..index];
13241    let after = &declaration[index + field_name.len()..];
13242    if before.chars().next_back().is_some_and(is_identifier_char)
13243        || after.chars().next().is_some_and(is_identifier_char)
13244    {
13245        return None;
13246    }
13247    Some(before.trim())
13248}
13249
13250fn normalize_field_type_text(type_text: &str) -> String {
13251    const FIELD_SPECIFIERS: [&str; 8] = [
13252        "extern ",
13253        "static ",
13254        "mutable ",
13255        "constexpr ",
13256        "constinit ",
13257        "inline ",
13258        "volatile ",
13259        "const ",
13260    ];
13261
13262    let mut normalized = normalize_type_text(type_text);
13263    loop {
13264        let Some(stripped) = FIELD_SPECIFIERS
13265            .iter()
13266            .find_map(|specifier| normalized.strip_prefix(specifier))
13267        else {
13268            return normalized;
13269        };
13270        normalized = normalize_type_text(stripped);
13271    }
13272}
13273
13274fn is_identifier_char(ch: char) -> bool {
13275    ch == '_' || ch.is_ascii_alphanumeric()
13276}
13277
13278pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13279    let Some(type_node) = node.child_by_field_name("type") else {
13280        return false;
13281    };
13282    ctx.visibility.resolves_to_type(
13283        &ctx.analyzer,
13284        ctx.file,
13285        node_text(type_node, ctx.source),
13286        owner,
13287    )
13288}
13289
13290pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13291    !ctx.analyzer
13292        .declarations(ctx.file)
13293        .into_iter()
13294        .filter(|unit| unit.is_function())
13295        .any(|unit| {
13296            ctx.analyzer.ranges(&unit).iter().any(|range| {
13297                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13298            })
13299        })
13300}
13301
13302/// How a `T var ...;` declaration initializes its object.
13303pub enum DeclarationConstructorInitializer<'tree> {
13304    /// Direct initialization, `T var(args)` or `T var{args}`: the argument list
13305    /// the declaration hands the constructor.
13306    Arguments(Node<'tree>),
13307    /// Copy initialization from one expression, `T var = expr`, which supplies a
13308    /// single constructor argument without spelling an argument list.
13309    Expression(Node<'tree>),
13310    /// `T var;`, which names no constructor argument at all.
13311    Empty,
13312}
13313
13314pub fn declaration_constructor_initializer(
13315    node: Node<'_>,
13316) -> DeclarationConstructorInitializer<'_> {
13317    let mut cursor = node.walk();
13318    for child in node.named_children(&mut cursor) {
13319        if child.kind() == "init_declarator" {
13320            let Some(value) = child
13321                .child_by_field_name("value")
13322                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13323                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13324            else {
13325                return DeclarationConstructorInitializer::Empty;
13326            };
13327            return match value.kind() {
13328                "argument_list" | "initializer_list" => {
13329                    DeclarationConstructorInitializer::Arguments(value)
13330                }
13331                "compound_literal_expression" => call_arguments_node(value)
13332                    .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13333                        DeclarationConstructorInitializer::Arguments(arguments)
13334                    }),
13335                _ => DeclarationConstructorInitializer::Expression(value),
13336            };
13337        }
13338        if let Some(declarator) = declaration_declarator(node, child) {
13339            return declarator_parameters(declarator)
13340                .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13341                    DeclarationConstructorInitializer::Arguments(parameters)
13342                });
13343        }
13344    }
13345    DeclarationConstructorInitializer::Empty
13346}
13347
13348pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13349    match declaration_constructor_initializer(node) {
13350        DeclarationConstructorInitializer::Arguments(arguments) => {
13351            argument_children(arguments).count()
13352        }
13353        DeclarationConstructorInitializer::Expression(_) => 1,
13354        DeclarationConstructorInitializer::Empty => 0,
13355    }
13356}
13357
13358/// The parameter list of the innermost declarator, which is where a
13359/// `T var(args)` declaration parsed as a function declarator keeps the
13360/// constructor arguments.
13361fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13362    let mut current = node;
13363    loop {
13364        if let Some(parameters) = current.child_by_field_name("parameters") {
13365            return Some(parameters);
13366        }
13367        current = current.child_by_field_name("declarator")?;
13368    }
13369}
13370
13371pub(super) fn first_named_child_of_kind<'tree>(
13372    node: Node<'tree>,
13373    kind: &str,
13374) -> Option<Node<'tree>> {
13375    let mut cursor = node.walk();
13376    node.named_children(&mut cursor)
13377        .find(|child| child.kind() == kind)
13378}
13379
13380fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13381    let mut stack = vec![root];
13382    while let Some(node) = stack.pop() {
13383        if node.kind() == kind {
13384            return Some(node);
13385        }
13386        push_named_children_reversed(node, &mut stack);
13387    }
13388    None
13389}
13390
13391fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13392    if node.kind() == "identifier" {
13393        return true;
13394    }
13395    if node.kind() == "parenthesized_expression" {
13396        return false;
13397    }
13398    if node.kind() == "call_expression" {
13399        return node
13400            .child_by_field_name("function")
13401            .is_some_and(|function| function.kind() == "identifier");
13402    }
13403    let mut stack = vec![node];
13404    while let Some(descendant) = stack.pop() {
13405        if descendant != node && descendant.kind() == "parenthesized_expression" {
13406            continue;
13407        }
13408        if descendant.kind() == "identifier" {
13409            return true;
13410        }
13411        if descendant.kind() == "call_expression" {
13412            if descendant
13413                .child_by_field_name("function")
13414                .is_some_and(|function| function.kind() == "identifier")
13415            {
13416                return true;
13417            }
13418            continue;
13419        }
13420        push_named_children_reversed(descendant, &mut stack);
13421    }
13422    false
13423}
13424
13425fn macro_expansion_shape_is_safe(
13426    node: Node<'_>,
13427    source: &str,
13428    parameters: &[String],
13429    environment: &MacroEnvironment,
13430) -> bool {
13431    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
13432        return true;
13433    }
13434    if node.kind() == "call_expression" {
13435        let Some(function) = node.child_by_field_name("function") else {
13436            return true;
13437        };
13438        if function.kind() != "identifier" {
13439            return true;
13440        }
13441        let function_name = node_text(function, source);
13442        if parameters
13443            .iter()
13444            .any(|parameter| parameter == function_name)
13445        {
13446            return false;
13447        }
13448        if !environment.may_bind(function_name) {
13449            return true;
13450        }
13451        let Some(arguments) = node.child_by_field_name("arguments") else {
13452            return false;
13453        };
13454        return argument_children(arguments).all(|argument| {
13455            if argument.kind() == "identifier"
13456                && parameters
13457                    .iter()
13458                    .any(|parameter| parameter == node_text(argument, source))
13459            {
13460                return false;
13461            }
13462            macro_expansion_shape_is_safe(argument, source, parameters, environment)
13463        });
13464    }
13465    let mut stack = vec![node];
13466    while let Some(descendant) = stack.pop() {
13467        if descendant != node {
13468            if descendant.kind() == "parenthesized_expression" {
13469                continue;
13470            }
13471            if descendant.kind() == "call_expression" {
13472                let expands = descendant
13473                    .child_by_field_name("function")
13474                    .filter(|function| function.kind() == "identifier")
13475                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
13476                if expands {
13477                    return false;
13478                }
13479                continue;
13480            }
13481        }
13482        if descendant.kind() == "identifier" {
13483            let identifier = node_text(descendant, source);
13484            if parameters.iter().any(|parameter| parameter == identifier)
13485                || environment.may_bind(identifier)
13486            {
13487                return false;
13488            }
13489        }
13490        push_named_children_reversed(descendant, &mut stack);
13491    }
13492    true
13493}
13494
13495fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
13496    let text = node_text(path, source);
13497    match path.kind() {
13498        "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
13499        "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
13500        _ => None,
13501    }
13502}
13503
13504fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
13505    let source = prepared.source();
13506    let mut facts = Vec::new();
13507    let mut nodes = vec![prepared.tree().root_node()];
13508    while let Some(node) = nodes.pop() {
13509        if node.kind() == "preproc_include" {
13510            let Some(path) = node
13511                .child_by_field_name("path")
13512                .and_then(|path| structured_include_path(path, source))
13513                .map(str::to_owned)
13514            else {
13515                continue;
13516            };
13517            facts.push(StructuredIncludeFact {
13518                start_byte: node.start_byte(),
13519                end_byte: node.end_byte(),
13520                path,
13521            });
13522            continue;
13523        }
13524        push_named_children_reversed(node, &mut nodes);
13525    }
13526    Arc::from(facts.into_boxed_slice())
13527}
13528
13529fn has_unresolved_include_visible_before_in_prepared(
13530    file: &ProjectFile,
13531    prepared: &PreparedSyntaxTree,
13532    include_targets: &IncludeTargetIndex,
13533    facts: &[StructuredIncludeFact],
13534    before_byte: usize,
13535) -> bool {
13536    let guards = OnceCell::new();
13537    let reference = CallableReferenceContext {
13538        file,
13539        position: Some(CallableReferencePosition {
13540            prepared,
13541            byte: before_byte,
13542            guards: &guards,
13543        }),
13544    };
13545    let root = prepared.tree().root_node();
13546    facts
13547        .iter()
13548        .filter(|fact| fact.end_byte <= before_byte)
13549        .any(|fact| {
13550            let node = root
13551                .descendant_for_byte_range(fact.start_byte, fact.end_byte)
13552                .expect("structured include fact range must be in prepared tree");
13553            assert_eq!(
13554                node.kind(),
13555                "preproc_include",
13556                "structured include fact range must identify its include node"
13557            );
13558            callable_preprocessor_context_is_visible_for_reference(
13559                node,
13560                prepared.source(),
13561                &reference,
13562            ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
13563        })
13564}
13565
13566fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
13567    let descendant = node;
13568    while let Some(parent) = node.parent() {
13569        if is_preprocessor_conditional(parent)
13570            && !is_file_covering_include_guard(parent, source)
13571            && preprocessor_conditional_contains_descendant(parent, descendant)
13572        {
13573            return true;
13574        }
13575        node = parent;
13576    }
13577    false
13578}
13579
13580/// The [`OwningPreprocessorConditionals`] of a macro event at `event`.
13581///
13582/// The descendant this walks up from is the one
13583/// [`VisibilityIndex::macro_event_condition_value`] starts from -- the
13584/// innermost node at the event's first byte, not the event node itself --
13585/// because containment compares that descendant's end against the recovered
13586/// conditional boundary, and the two nodes end in different places. An event
13587/// that [`has_preprocessor_conditional_ancestor`] rejects owns nothing: that
13588/// predicate is what has always decided whether an event is conditional at
13589/// all, and answering it first also skips the walk for the ordinary
13590/// unconditional event.
13591fn owning_preprocessor_conditionals(
13592    root: Node<'_>,
13593    event: Node<'_>,
13594    source: &str,
13595) -> OwningPreprocessorConditionals {
13596    if !has_preprocessor_conditional_ancestor(event, source) {
13597        return OwningPreprocessorConditionals::default();
13598    }
13599    let start = event.start_byte();
13600    let descendant = root
13601        .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
13602        .expect("a byte inside the parsed tree names a descendant");
13603    let mut owners = Vec::new();
13604    let mut current = descendant.parent();
13605    while let Some(conditional) = current {
13606        if is_preprocessor_conditional(conditional)
13607            && !is_file_covering_include_guard(conditional, source)
13608            && preprocessor_conditional_contains_descendant(conditional, descendant)
13609        {
13610            owners.push(conditional.start_byte());
13611        }
13612        current = conditional.parent();
13613    }
13614    owners.into_boxed_slice()
13615}
13616
13617fn is_preprocessor_conditional(node: Node<'_>) -> bool {
13618    matches!(
13619        node.kind(),
13620        "preproc_if"
13621            | "preproc_ifdef"
13622            | "preproc_ifndef"
13623            | "preproc_elif"
13624            | "preproc_elifdef"
13625            | "preproc_else"
13626    )
13627}
13628
13629fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
13630    node.parent()
13631        .filter(|parent| parent.kind() == "translation_unit")
13632        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
13633        && is_canonical_include_guard(node, source)
13634}
13635
13636fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
13637    if node.kind() != "preproc_ifdef"
13638        || node
13639            .child(0)
13640            .is_none_or(|directive| directive.kind() != "#ifndef")
13641        || node.child_by_field_name("alternative").is_some()
13642    {
13643        return false;
13644    }
13645    let Some(guard_name) = node.child_by_field_name("name") else {
13646        return false;
13647    };
13648    let mut cursor = node.walk();
13649    node.named_children(&mut cursor)
13650        .find(|child| *child != guard_name && child.kind() != "comment")
13651        .filter(|child| child.kind() == "preproc_def")
13652        .and_then(|definition| definition.child_by_field_name("name"))
13653        .is_some_and(|defined_name| {
13654            node_text(defined_name, source) == node_text(guard_name, source)
13655        })
13656}
13657
13658fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
13659    let mut guard = None;
13660    for child in named_children_iter(root) {
13661        if child.kind() == "comment" || is_pragma_once(child, source) {
13662            continue;
13663        }
13664        if guard.is_none() && is_canonical_include_guard(child, source) {
13665            guard = Some(child);
13666        } else {
13667            return None;
13668        }
13669    }
13670    guard
13671        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
13672        .map(|name| node_text(name, source).to_string())
13673}
13674
13675fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
13676    if (0..root.named_child_count())
13677        .filter_map(|index| root.named_child(index))
13678        .any(|child| is_pragma_once(child, source))
13679    {
13680        return MacroIncludeProtection::PragmaOnce;
13681    }
13682    top_level_canonical_include_guard_name(root, source)
13683        .map(MacroIncludeProtection::MacroGuard)
13684        .unwrap_or(MacroIncludeProtection::None)
13685}
13686
13687fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
13688    node.kind() == "preproc_call"
13689        && node
13690            .child_by_field_name("directive")
13691            .is_some_and(|directive| node_text(directive, source) == "#pragma")
13692        && node
13693            .child_by_field_name("argument")
13694            .is_some_and(|argument| node_text(argument, source).trim() == "once")
13695}
13696
13697fn parse_preproc_identifier(argument: &str) -> Option<String> {
13698    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
13699    let mut parser = Parser::new();
13700    parser
13701        .set_language(&tree_sitter_cpp::LANGUAGE.into())
13702        .ok()?;
13703    let tree = parser.parse(&sentinel, None)?;
13704    if tree.root_node().has_error() {
13705        return None;
13706    }
13707    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
13708    let identifier = statement.named_child(0)?;
13709    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
13710        .then(|| node_text(identifier, &sentinel).to_string())
13711}
13712
13713pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
13714    match node.kind() {
13715        "identifier" | "field_identifier" => {
13716            let name = node_text(node, source).trim();
13717            (!name.is_empty()).then(|| name.to_string())
13718        }
13719        "abstract_array_declarator"
13720        | "abstract_function_declarator"
13721        | "abstract_parenthesized_declarator"
13722        | "abstract_pointer_declarator"
13723        | "abstract_reference_declarator" => None,
13724        "function_declarator" => node
13725            .child_by_field_name("declarator")
13726            .or_else(|| node.child_by_field_name("name"))
13727            .and_then(|child| extract_variable_name(child, source)),
13728        _ => node
13729            .child_by_field_name("declarator")
13730            .or_else(|| node.child_by_field_name("name"))
13731            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
13732            .and_then(|child| extract_variable_name(child, source)),
13733    }
13734}
13735
13736/// Whether `file` is proven to use plain-C source semantics.
13737///
13738/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
13739/// compilation dialect on their own, so only an exact `.c` source extension is
13740/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
13741/// identifiers.
13742///
13743/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
13744/// which extraction reads too (a `.c` file is extracted with C tag scope), so
13745/// the doctrine has exactly one definition.
13746pub fn is_c_source_file(file: &ProjectFile) -> bool {
13747    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
13748}
13749
13750/// Whether tree-sitter parsed the operand of C `sizeof(T)` as an expression
13751/// identifier even though `T` may denote a typedef.
13752///
13753/// The grammar cannot distinguish `sizeof(value)` from `sizeof(Type)` without
13754/// semantic information. Keep this helper structural and narrow; callers must
13755/// still prove a visible type and reject an active ordinary-namespace shadow.
13756pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
13757    if !is_c_source_file(file) || node.kind() != "identifier" {
13758        return false;
13759    }
13760    let mut operand = node;
13761    while let Some(parent) = operand.parent().filter(|parent| {
13762        parent.kind() == "parenthesized_expression"
13763            && parent.named_child_count() == 1
13764            && parent.named_child(0) == Some(operand)
13765    }) {
13766        operand = parent;
13767    }
13768    operand.parent().is_some_and(|parent| {
13769        parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
13770    })
13771}
13772
13773/// Return the type and member leaves of a C `offsetof` member designator.
13774///
13775/// `offsetof_expression` is a dedicated tree-sitter node, so its two operands
13776/// must be interpreted through their named fields.  In particular, do not
13777/// infer the aggregate from the enclosing lexical scope: an `offsetof` can
13778/// name a member of an unrelated aggregate, including a field promoted from
13779/// an anonymous union.  A missing or unsupported operand is deliberately
13780/// rejected so callers can keep the reference unresolved.
13781pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
13782    if node.kind() != "field_identifier" {
13783        return None;
13784    }
13785    let expression = node.parent().filter(|parent| {
13786        parent.kind() == "offsetof_expression" && parent.child_by_field_name("member") == Some(node)
13787    })?;
13788    if expression.has_error() {
13789        return None;
13790    }
13791    let closing = expression.child(expression.child_count().saturating_sub(1))?;
13792    if closing.kind() != ")" || closing.is_missing() {
13793        return None;
13794    }
13795    let type_descriptor = expression.child_by_field_name("type")?;
13796    if type_descriptor.kind() != "type_descriptor"
13797        || type_descriptor.is_missing()
13798        || type_descriptor.has_error()
13799    {
13800        return None;
13801    }
13802    let type_specifier = type_descriptor.child_by_field_name("type")?;
13803    if type_specifier.is_missing() || type_specifier.has_error() {
13804        return None;
13805    }
13806    let type_reference = match type_specifier.kind() {
13807        "class_specifier" | "struct_specifier" | "union_specifier" => {
13808            type_specifier.child_by_field_name("name")?
13809        }
13810        _ => type_specifier,
13811    };
13812    (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
13813}
13814
13815/// Whether `node` is the member leaf of an `offsetof_expression`, including a
13816/// malformed type operand.  Callers use this guard to prevent the ordinary
13817/// field-name heuristics from guessing an owner after structured resolution
13818/// has failed.
13819pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
13820    node.kind() == "field_identifier"
13821        && node.parent().is_some_and(|parent| {
13822            parent.kind() == "offsetof_expression"
13823                && parent.child_by_field_name("member") == Some(node)
13824        })
13825}
13826
13827/// Whether `node` is a template argument name that tree-sitter spelled with
13828/// type syntax.
13829///
13830/// The grammar cannot tell a type argument from a non-type (value) argument, so
13831/// it gives both the same shape:
13832/// `template_argument_list -> type_descriptor -> type_identifier`. In
13833/// `std::array<W, N>` the type `W` and the constant `N` parse identically, and
13834/// so do `std::span<const uint8_t, ED448_LEN>`'s length and a nested type
13835/// member used as a real type argument.
13836///
13837/// This helper reports only the syntactic position. A caller must still prove
13838/// which namespace explains the spelling: forward navigation asks the type
13839/// namespace first and reads the leaf as a value only when no type explains it,
13840/// and the inverse field scan admits the leaf only when no visible type does
13841/// (#2556).
13842pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
13843    if node.kind() != "type_identifier" {
13844        return false;
13845    }
13846    let Some(descriptor) = node
13847        .parent()
13848        .filter(|parent| parent.kind() == "type_descriptor")
13849    else {
13850        return false;
13851    };
13852    if descriptor.child_by_field_name("type") != Some(node) {
13853        return false;
13854    }
13855    let Some(arguments) = descriptor
13856        .parent()
13857        .filter(|parent| parent.kind() == "template_argument_list")
13858    else {
13859        return false;
13860    };
13861    arguments.parent().is_some_and(|owner| {
13862        matches!(
13863            owner.kind(),
13864            "template_type" | "template_function" | "template_method"
13865        ) && owner.child_by_field_name("arguments") == Some(arguments)
13866    })
13867}
13868
13869/// Whether a reference written in `file` reads C++ source with C semantics.
13870///
13871/// [`is_c_source_file`] answers the half a path settles on its own. The other
13872/// half is a header, which has no dialect of its own: it is read as C exactly
13873/// when every workspace translation unit that provably compiles it compiles it
13874/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
13875///
13876/// This is the gate for anything that is really about the compilation
13877/// language of the code being read -- which reading of an included header's
13878/// declarations is in scope, whether `this` is an ordinary identifier. It is
13879/// NOT the gate for a question that is genuinely about a `.c` file on disk;
13880/// those keep calling [`is_c_source_file`].
13881pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
13882    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
13883}
13884
13885pub fn is_declarator_node(node: Node<'_>) -> bool {
13886    matches!(
13887        node.kind(),
13888        "identifier"
13889            | "field_identifier"
13890            | "qualified_identifier"
13891            | "scoped_identifier"
13892            | "pointer_declarator"
13893            | "reference_declarator"
13894            | "array_declarator"
13895            | "parenthesized_declarator"
13896            | "function_declarator"
13897    )
13898}
13899
13900/// Returns the declarator represented by one direct child of a declaration.
13901///
13902/// Tree-sitter exposes the first declarator through the `declarator` field,
13903/// but subsequent comma-separated declarators are unfielded direct children:
13904/// `int first, second;` therefore has an `identifier` child for `first` and a
13905/// second, otherwise identical, `identifier` child for `second`. Consumers
13906/// must inspect both shapes or a later declarator can be mistaken for a use of
13907/// an unrelated indexed symbol.
13908pub fn declaration_declarator<'tree>(
13909    declaration: Node<'tree>,
13910    child: Node<'tree>,
13911) -> Option<Node<'tree>> {
13912    if !matches!(
13913        declaration.kind(),
13914        "declaration"
13915            | "field_declaration"
13916            | "parameter_declaration"
13917            | "optional_parameter_declaration"
13918            | "function_definition"
13919            | "type_definition"
13920            | "alias_declaration"
13921            | "template_instantiation"
13922    ) {
13923        return None;
13924    }
13925    if declaration
13926        .child_by_field_name("type")
13927        .is_some_and(|type_node| same_node(type_node, child))
13928    {
13929        return None;
13930    }
13931    if child.kind() == "init_declarator" {
13932        return child.child_by_field_name("declarator");
13933    }
13934    let field = field_name_in_parent(declaration, child);
13935    if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
13936        || (declaration.kind() == "type_definition"
13937            && child.kind() == "type_identifier"
13938            && matches!(field, Some("declarator") | None))
13939    {
13940        Some(child)
13941    } else {
13942        None
13943    }
13944}
13945
13946/// One run of a container's children whose parsed namespaces differ from
13947/// their lexical namespaces, with the complete lexical namespace path.
13948#[derive(Clone, Debug, PartialEq, Eq)]
13949pub struct RecoveredNamespaceRegion {
13950    /// Start byte of the first child in the run.
13951    pub start: usize,
13952    /// End byte of the last child in the run.
13953    pub end: usize,
13954    /// The complete enclosing namespace path of the run, outermost first.
13955    /// This can be empty when a parsed namespace extends past its real close.
13956    pub components: Vec<String>,
13957}
13958
13959/// The namespaces C++ parse recovery drops from a file's tree.
13960///
13961/// When tree-sitter cannot parse a construct inside a namespace body it closes
13962/// an inner scope with a MISSING brace, or skips an opening brace into an
13963/// ERROR node. Every real `}` after that then closes one scope too early: a
13964/// class body's `}` closes the namespace, the namespace's own `}` closes its
13965/// parent, and the outermost real closes land in a trailing ERROR node. The
13966/// declarations between a stolen close and the real one keep their byte
13967/// positions but lose their `namespace_definition` ancestors (Catch2's
13968/// `catch_matchers_templated.hpp`, issue #1537).
13969///
13970/// A file-global stack over real AST brace tokens restores both lost and
13971/// overextended namespaces. Missing braces do nothing; error-free subtrees are
13972/// balanced and can be skipped. For each child of a damaged container, compare
13973/// the namespaces still open on that stack with the parsed ancestor path. A
13974/// difference yields a region carrying the complete lexical path, including
13975/// an empty path when recovery swallowed file-scope declarations. Consecutive
13976/// children sharing that path form one [`RecoveredNamespaceRegion`]. The same
13977/// pass retains matching brace positions for declaration partitioning (#3087).
13978/// A file without parse errors needs neither correction nor a brace index.
13979#[derive(Clone, Debug, Default)]
13980pub struct OrphanedNamespaceScopeIndex {
13981    regions: Vec<RecoveredNamespaceRegion>,
13982    brace_closes: HashMap<usize, Range>,
13983}
13984
13985impl OrphanedNamespaceScopeIndex {
13986    pub fn build(root: Node<'_>, source: &str) -> Self {
13987        if !root.has_error() {
13988            return Self::default();
13989        }
13990        struct Frame<'tree> {
13991            node: Node<'tree>,
13992            children: std::vec::IntoIter<Node<'tree>>,
13993            parsed_scope: Vec<String>,
13994            run: Option<RecoveredNamespaceRegion>,
13995        }
13996        fn frame<'tree>(
13997            node: Node<'tree>,
13998            mut parsed_scope: Vec<String>,
13999            source: &str,
14000        ) -> Frame<'tree> {
14001            if node.kind() == "namespace_definition"
14002                && let Some(name) = node.child_by_field_name("name")
14003            {
14004                let mut components = Vec::new();
14005                if append_cpp_name_components(name, source, &mut components).is_some() {
14006                    parsed_scope.extend(components);
14007                }
14008            }
14009            let mut cursor = node.walk();
14010            Frame {
14011                node,
14012                children: node.children(&mut cursor).collect::<Vec<_>>().into_iter(),
14013                parsed_scope,
14014                run: None,
14015            }
14016        }
14017        let mut regions = Vec::new();
14018        let mut brace_closes = HashMap::default();
14019        // The brace stack belongs to the file, not to a parser frame. A real
14020        // close inside a damaged child can close its parent's namespace. The
14021        // following children must see that removal immediately (#3087).
14022        let mut open = Vec::new();
14023        let mut lexical_scope = Vec::new();
14024        let mut frames = vec![frame(root, Vec::new(), source)];
14025        while let Some(current) = frames.last_mut() {
14026            let Some(child) = current.children.next() else {
14027                regions.extend(frames.pop().expect("the frame just borrowed").run);
14028                continue;
14029            };
14030            match child.kind() {
14031                "{" if !child.is_missing() => {
14032                    regions.extend(current.run.take());
14033                    let mut components = current
14034                        .node
14035                        .parent()
14036                        .map(|parent| namespace_body_name_components(parent, current.node, source))
14037                        .unwrap_or_default();
14038                    if components.is_empty() {
14039                        components = recovered_namespace_open_components(child, source);
14040                    }
14041                    open.push((child.start_byte(), lexical_scope.len()));
14042                    lexical_scope.extend(components);
14043                    continue;
14044                }
14045                "}" if !child.is_missing() => {
14046                    regions.extend(current.run.take());
14047                    if let Some((start, namespace_len)) = open.pop() {
14048                        lexical_scope.truncate(namespace_len);
14049                        brace_closes.insert(
14050                            start,
14051                            Range {
14052                                start_byte: child.start_byte(),
14053                                end_byte: child.end_byte(),
14054                                start_line: child.start_position().row + 1,
14055                                end_line: child.end_position().row + 1,
14056                            },
14057                        );
14058                    }
14059                    continue;
14060                }
14061                _ => {}
14062            }
14063            // The namespace head and body precede their opening brace; their
14064            // own parsed namespace is not yet on the lexical stack. Compare
14065            // the items inside the body, not this header bookkeeping.
14066            if current.node.kind() != "namespace_definition"
14067                && lexical_scope != current.parsed_scope
14068            {
14069                match &mut current.run {
14070                    Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14071                    run => {
14072                        regions.extend(run.take());
14073                        *run = Some(RecoveredNamespaceRegion {
14074                            start: child.start_byte(),
14075                            end: child.end_byte(),
14076                            components: lexical_scope.clone(),
14077                        });
14078                    }
14079                }
14080            } else {
14081                regions.extend(current.run.take());
14082            }
14083            // Error-free subtrees are balanced. Keeping them intact also
14084            // leaves their valid nested namespaces to the ordinary ancestry
14085            // walk in restore_enclosing_namespaces.
14086            if child.has_error() {
14087                let parsed_scope = current.parsed_scope.clone();
14088                frames.push(frame(child, parsed_scope, source));
14089            }
14090        }
14091        Self {
14092            regions,
14093            brace_closes,
14094        }
14095    }
14096
14097    /// The real AST brace matching an opening brace in a damaged subtree.
14098    /// Missing tokens and braces inside comments or literals do not participate.
14099    pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14100        self.brace_closes.get(&open).copied()
14101    }
14102
14103    pub fn is_empty(&self) -> bool {
14104        self.regions.is_empty()
14105    }
14106
14107    /// The bytes this index holds, for the analyzer cache's weight.
14108    pub fn approximate_size(&self) -> usize {
14109        self.regions.iter().fold(
14110            self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14111            |total, region| {
14112                total
14113                    .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14114                    .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14115            },
14116        )
14117    }
14118
14119    /// The innermost recovered region containing `byte`.
14120    pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14121        self.regions
14122            .iter()
14123            .filter(|region| region.start <= byte && byte < region.end)
14124            .min_by_key(|region| region.end - region.start)
14125    }
14126
14127    /// The enclosing namespaces of `node`, outermost first, restoring the ones
14128    /// parse recovery dropped from its ancestor chain. The one answer both
14129    /// lookup directions and declaration collection use (issue #1537).
14130    pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14131        let mut parsed = Vec::new();
14132        let mut current = node.parent();
14133        while let Some(parent) = current {
14134            if parent.kind() == "namespace_definition"
14135                && let Some(name) = parent.child_by_field_name("name")
14136            {
14137                let mut components = Vec::new();
14138                if append_cpp_name_components(name, source, &mut components).is_some() {
14139                    parsed.push((parent.start_byte(), components));
14140                }
14141            }
14142            current = parent.parent();
14143        }
14144        parsed.reverse();
14145        self.restore_enclosing_namespaces(parsed, node.start_byte())
14146    }
14147
14148    /// [`Self::enclosing_namespace_components`] for a caller that has already
14149    /// climbed the ancestor chain: `parsed` lists the node's named
14150    /// `namespace_definition` ancestors outermost first, each with its start
14151    /// byte. A region covering the node supplies every namespace outside it;
14152    /// only the parsed ancestors that start inside the region still apply.
14153    pub fn restore_enclosing_namespaces(
14154        &self,
14155        parsed: Vec<(usize, Vec<String>)>,
14156        node_start: usize,
14157    ) -> Vec<String> {
14158        let Some(region) = self.region_at(node_start) else {
14159            return parsed
14160                .into_iter()
14161                .flat_map(|(_, components)| components)
14162                .collect();
14163        };
14164        region
14165            .components
14166            .iter()
14167            .cloned()
14168            .chain(
14169                parsed
14170                    .into_iter()
14171                    .filter(|(start, _)| *start >= region.start)
14172                    .flat_map(|(_, components)| components),
14173            )
14174            .collect()
14175    }
14176}
14177
14178/// The name components of the namespace a stray `{` opens, or empty when `open`
14179/// does not follow a namespace head.
14180///
14181/// When a namespace body holds a construct tree-sitter cannot parse, recovery
14182/// can collapse the whole `namespace Name { ... }` into one `ERROR` instead of
14183/// a `namespace_definition`: the head survives as the node's own `namespace`
14184/// keyword, name and `{` tokens, in that order, and everything the namespace
14185/// declares becomes a flat sibling of them (Catch2's `catch_decomposer.hpp`,
14186/// issue #3084). Read the head from those siblings so the brace stack names
14187/// the scope the brace opens. An anonymous namespace has no representable
14188/// name and keeps an opaque scope.
14189fn recovered_namespace_open_components(open: Node<'_>, source: &str) -> Vec<String> {
14190    let mut head = Vec::new();
14191    let mut previous = open.prev_sibling();
14192    while let Some(node) = previous {
14193        if node.kind() != "comment" {
14194            head.push(node);
14195            if head.len() == 2 {
14196                break;
14197            }
14198        }
14199        previous = node.prev_sibling();
14200    }
14201    let [name, keyword] = head[..] else {
14202        return Vec::new();
14203    };
14204    if keyword.kind() != "namespace" {
14205        return Vec::new();
14206    }
14207    let mut components = Vec::new();
14208    if append_cpp_name_components(name, source, &mut components).is_none() {
14209        components.clear();
14210    }
14211    components
14212}
14213
14214/// The name components of the namespace whose body `body` is, or empty when
14215/// `body` is not the body of a named `namespace_definition` `parent`.
14216fn namespace_body_name_components(parent: Node<'_>, body: Node<'_>, source: &str) -> Vec<String> {
14217    let mut components = Vec::new();
14218    if body.kind() == "declaration_list"
14219        && parent.kind() == "namespace_definition"
14220        && parent.child_by_field_name("body") == Some(body)
14221        && let Some(name) = parent.child_by_field_name("name")
14222        && append_cpp_name_components(name, source, &mut components).is_none()
14223    {
14224        components.clear();
14225    }
14226    components
14227}
14228
14229#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14230pub enum RecoveredDeclaratorTypeContext {
14231    Declaration,
14232    FunctionDefinition,
14233    Parameter,
14234}
14235
14236/// Recognize a real type displaced into a qualified declarator by parser
14237/// recovery.
14238///
14239/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
14240/// type and `Result` were the scope of a qualified declarator with a missing
14241/// `::`. A template return such as `API Result<T> make()` uses a
14242/// `template_type` for the same recovered scope. The same recovery occurs for
14243/// macro-prefixed definitions, extern variables, and macro-decorated
14244/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
14245/// the macro). Keep this intentionally structural: the recovered scope must
14246/// have the grammar's missing separator, the qualified node must occupy the
14247/// declaration's declarator chain, a separate nonempty type must occupy the
14248/// normal type field, and the recovered name must unwrap to a real declarator
14249/// name.
14250pub fn recovered_macro_decorated_declarator_type(
14251    node: Node<'_>,
14252) -> Option<RecoveredDeclaratorTypeContext> {
14253    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
14254}
14255
14256/// Return the declaration/function `type` displaced by a macro-shaped
14257/// qualified declarator, together with the enclosing declaration context.
14258/// Callers use the macro scope only as structural admission evidence; the
14259/// returned node is the real type reference to resolve and record.
14260pub fn recovered_macro_decorated_type_node(
14261    node: Node<'_>,
14262) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14263    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
14264        return None;
14265    }
14266    let qualified = node.parent()?;
14267    if qualified.kind() != "qualified_identifier"
14268        || qualified.child_by_field_name("scope") != Some(node)
14269        || !(0..qualified.child_count())
14270            .filter_map(|index| qualified.child(index))
14271            .any(|child| child.kind() == "::" && child.is_missing())
14272    {
14273        return None;
14274    }
14275    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
14276        return None;
14277    }
14278
14279    let (declaration, context) = recovered_declarator_container(qualified)?;
14280    let type_node = declaration
14281        .child_by_field_name("type")
14282        .filter(|type_node| {
14283            *type_node != qualified
14284                && !type_node.is_missing()
14285                && type_node.start_byte() != type_node.end_byte()
14286        })?;
14287    Some((type_node, context))
14288}
14289
14290fn recovered_declarator_container(
14291    mut declarator: Node<'_>,
14292) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
14293    loop {
14294        let parent = declarator.parent()?;
14295        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
14296            return Some((
14297                parent
14298                    .parent()
14299                    .filter(|declaration| declaration.kind() == "declaration")?,
14300                RecoveredDeclaratorTypeContext::Declaration,
14301            ));
14302        }
14303        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
14304            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
14305        }
14306        if parent.kind() == "function_definition"
14307            && has_field_child(parent, "declarator", declarator)
14308        {
14309            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
14310        }
14311        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
14312        // level down: the parameter's `type` field takes the macro token and
14313        // the real type `T` becomes the recovered scope of the declarator.
14314        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
14315        // candidate at all (#1830).
14316        if matches!(
14317            parent.kind(),
14318            "parameter_declaration" | "optional_parameter_declaration"
14319        ) && has_field_child(parent, "declarator", declarator)
14320        {
14321            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
14322        }
14323        if !matches!(
14324            parent.kind(),
14325            "array_declarator"
14326                | "function_declarator"
14327                | "parenthesized_declarator"
14328                | "pointer_declarator"
14329                | "pointer_type_declarator"
14330                | "reference_declarator"
14331        ) || !has_field_child(parent, "declarator", declarator)
14332        {
14333            return None;
14334        }
14335        declarator = parent;
14336    }
14337}
14338
14339fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
14340    let mut cursor = parent.walk();
14341    parent
14342        .children_by_field_name(field, &mut cursor)
14343        .any(|child| child == target)
14344}
14345
14346fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
14347    loop {
14348        if node.is_missing() || node.start_byte() == node.end_byte() {
14349            return false;
14350        }
14351        match node.kind() {
14352            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
14353                return true;
14354            }
14355            "array_declarator"
14356            | "function_declarator"
14357            | "parenthesized_declarator"
14358            | "pointer_declarator"
14359            | "pointer_type_declarator"
14360            | "reference_declarator" => {
14361                let Some(declarator) = node.child_by_field_name("declarator") else {
14362                    return false;
14363                };
14364                node = declarator;
14365            }
14366            _ => return false,
14367        }
14368    }
14369}
14370
14371/// Aggregate-owner proof for a structurally recognized designated initializer.
14372pub enum DesignatedInitializerOwner {
14373    Resolved(CodeUnit),
14374    Unresolved,
14375}
14376
14377enum InitializerOwnerStep {
14378    Field(String),
14379    AggregateWrapper,
14380}
14381
14382/// Recognize a designated-initializer field and, when possible, resolve its
14383/// aggregate owner.
14384///
14385/// Covers both the grammar's ordinary `field_designator` shape and the exact
14386/// recovery used for `.field = value` after a preprocessor-split array
14387/// initializer. Ordered multi-component designators follow each preceding
14388/// field's declared aggregate type. `None` means the node is not a designator
14389/// at all; an unresolved designator remains classified so callers cannot fall
14390/// through to unrelated global/member heuristics.
14391pub fn designated_initializer_owner(
14392    analyzer: &CppGraphSource<'_>,
14393    visibility: &VisibilityIndex<'_>,
14394    file: &ProjectFile,
14395    source: &str,
14396    node: Node<'_>,
14397) -> Option<DesignatedInitializerOwner> {
14398    if let Some(designator) = node
14399        .parent()
14400        .filter(|parent| parent.kind() == "field_designator")
14401    {
14402        let pair = designator.parent()?;
14403        if pair.kind() != "initializer_pair" {
14404            return None;
14405        }
14406        let mut cursor = pair.walk();
14407        let designators = pair
14408            .children_by_field_name("designator", &mut cursor)
14409            .collect::<Vec<_>>();
14410        let position = designators
14411            .iter()
14412            .position(|candidate| same_node(*candidate, designator))?;
14413        let initializer = pair.parent()?;
14414        if initializer.kind() != "initializer_list" {
14415            return None;
14416        }
14417        let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
14418        for prior in &designators[..position] {
14419            let field = prior
14420                .child_by_field_name("field")
14421                .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
14422            owner = owner.and_then(|owner| {
14423                initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
14424            });
14425        }
14426        return Some(classified_designated_owner(owner));
14427    }
14428
14429    let init_declarator = node.parent()?;
14430    if init_declarator.child_by_field_name("declarator") != Some(node)
14431        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
14432    {
14433        return None;
14434    }
14435    Some(classified_designated_owner(declaration_owner(
14436        analyzer,
14437        visibility,
14438        file,
14439        source,
14440        init_declarator.parent()?,
14441    )))
14442}
14443
14444fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
14445    owner.map_or(
14446        DesignatedInitializerOwner::Unresolved,
14447        DesignatedInitializerOwner::Resolved,
14448    )
14449}
14450
14451fn initializer_list_owner(
14452    analyzer: &CppGraphSource<'_>,
14453    visibility: &VisibilityIndex<'_>,
14454    file: &ProjectFile,
14455    source: &str,
14456    initializer: Node<'_>,
14457) -> Option<CodeUnit> {
14458    let mut current = initializer;
14459    let mut steps = Vec::new();
14460    loop {
14461        let parent = current.parent()?;
14462        match parent.kind() {
14463            "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
14464                let designator = parent.child_by_field_name("designator")?;
14465                let step = designator
14466                    .child_by_field_name("field")
14467                    .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
14468                    .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
14469                    .unwrap_or(InitializerOwnerStep::AggregateWrapper);
14470                steps.push(step);
14471                current = parent.parent()?;
14472            }
14473            "initializer_list" => {
14474                current = parent;
14475            }
14476            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
14477                let declaration = parent.parent()?;
14478                let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
14479                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14480            }
14481            "compound_literal_expression"
14482                if parent.child_by_field_name("value") == Some(current) =>
14483            {
14484                let type_node = parent.child_by_field_name("type")?;
14485                let owner =
14486                    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
14487                return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
14488            }
14489            "ERROR" => current = parent,
14490            _ => return None,
14491        }
14492    }
14493}
14494
14495fn apply_initializer_owner_steps(
14496    analyzer: &CppGraphSource<'_>,
14497    visibility: &VisibilityIndex<'_>,
14498    file: &ProjectFile,
14499    mut owner: CodeUnit,
14500    steps: Vec<InitializerOwnerStep>,
14501) -> Option<CodeUnit> {
14502    for step in steps.into_iter().rev() {
14503        if let InitializerOwnerStep::Field(field_name) = step {
14504            owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
14505        }
14506    }
14507    Some(owner)
14508}
14509
14510fn initializer_field_owner(
14511    analyzer: &CppGraphSource<'_>,
14512    visibility: &VisibilityIndex<'_>,
14513    file: &ProjectFile,
14514    owner: CodeUnit,
14515    field_name: &str,
14516) -> Option<CodeUnit> {
14517    let fields = visibility
14518        .visible_members_for_owner_name(file, &owner, field_name)
14519        .into_iter()
14520        .filter(|field| field.is_field())
14521        .collect::<Vec<_>>();
14522    let field = match fields.as_slice() {
14523        [field] => *field,
14524        _ => return None,
14525    };
14526    field_declared_binding(analyzer, visibility, file, field)?.unit
14527}
14528
14529fn declaration_owner(
14530    analyzer: &CppGraphSource<'_>,
14531    visibility: &VisibilityIndex<'_>,
14532    file: &ProjectFile,
14533    source: &str,
14534    declaration: Node<'_>,
14535) -> Option<CodeUnit> {
14536    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
14537        return None;
14538    }
14539    let type_node = declaration
14540        .child_by_field_name("type")
14541        .or_else(|| first_type_child(declaration))?;
14542    resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
14543}
14544
14545fn resolve_designated_owner_type(
14546    analyzer: &CppGraphSource<'_>,
14547    visibility: &VisibilityIndex<'_>,
14548    file: &ProjectFile,
14549    source: &str,
14550    type_node: Node<'_>,
14551) -> Option<CodeUnit> {
14552    if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
14553        return Some(owner);
14554    }
14555    let type_name = normalize_type_text(node_text(type_node, source));
14556    visibility
14557        .resolve_type(file, &type_name)
14558        .filter(CodeUnit::is_class)
14559}
14560
14561pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
14562    let mut cursor = node.walk();
14563    node.named_children(&mut cursor).find(|child| {
14564        matches!(
14565            child.kind(),
14566            "type_identifier"
14567                | "primitive_type"
14568                | "qualified_identifier"
14569                | "scoped_type_identifier"
14570                | "struct_specifier"
14571                | "union_specifier"
14572                | "enum_specifier"
14573        )
14574    })
14575}
14576
14577pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
14578    visibility: &VisibilityIndex<'_>,
14579    file: &ProjectFile,
14580    source: &str,
14581    declarator: Node<'_>,
14582    type_text: Option<&str>,
14583    bindings: &LocalInferenceEngine<T>,
14584) -> bool {
14585    if !has_ancestor_kind(declarator, "compound_statement") {
14586        return false;
14587    }
14588    if declarator
14589        .child_by_field_name("declarator")
14590        .is_none_or(|declarator| declarator.kind() != "identifier")
14591    {
14592        return false;
14593    }
14594    if !type_text
14595        .and_then(|text| visibility.resolve_type(file, text))
14596        .is_some_and(|unit| unit.is_class())
14597    {
14598        return false;
14599    }
14600    declarator
14601        .child_by_field_name("parameters")
14602        .is_some_and(|parameters| {
14603            constructor_parameters_look_like_expressions(parameters, source, bindings)
14604        })
14605}
14606
14607fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
14608    parameters: Node<'_>,
14609    source: &str,
14610    bindings: &LocalInferenceEngine<T>,
14611) -> bool {
14612    let mut cursor = parameters.walk();
14613    parameters.named_children(&mut cursor).any(|parameter| {
14614        !matches!(
14615            parameter.kind(),
14616            "parameter_declaration" | "optional_parameter_declaration"
14617        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
14618    })
14619}
14620
14621fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
14622    parameter: Node<'_>,
14623    source: &str,
14624    bindings: &LocalInferenceEngine<T>,
14625) -> bool {
14626    let text = node_text(parameter, source).trim();
14627    if text
14628        .chars()
14629        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
14630        && bindings.is_shadowed(text)
14631    {
14632        return true;
14633    }
14634
14635    let Some(base) = parameter
14636        .child_by_field_name("type")
14637        .filter(|base| base.kind() == "type_identifier")
14638    else {
14639        return false;
14640    };
14641    let Some(subscript) = parameter
14642        .child_by_field_name("declarator")
14643        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
14644    else {
14645        return false;
14646    };
14647    subscript.child_by_field_name("size").is_some()
14648        && bindings.is_shadowed(node_text(base, source).trim())
14649}
14650
14651pub fn is_declaration_name(node: Node<'_>) -> bool {
14652    let Some(parent) = node.parent() else {
14653        return false;
14654    };
14655    if parent
14656        .child_by_field_name("name")
14657        .is_some_and(|name| same_node(name, node))
14658    {
14659        if matches!(
14660            parent.kind(),
14661            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14662        ) {
14663            return cpp_tag_specifier_declares_name(parent);
14664        }
14665        if matches!(
14666            parent.kind(),
14667            "namespace_definition"
14668                | "namespace_alias_definition"
14669                | "alias_declaration"
14670                | "enumerator"
14671        ) {
14672            return true;
14673        }
14674    }
14675
14676    let mut current = Some(parent);
14677    while let Some(ancestor) = current {
14678        let type_definition = ancestor.kind() == "type_definition";
14679        let mut child_cursor = ancestor.walk();
14680        if ancestor.named_children(&mut child_cursor).any(|child| {
14681            declaration_declarator(ancestor, child).is_some_and(|declarator| {
14682                declarator_name_path_contains(declarator, node, type_definition)
14683            })
14684        }) {
14685            return true;
14686        }
14687        if matches!(
14688            ancestor.kind(),
14689            "declaration"
14690                | "field_declaration"
14691                | "parameter_declaration"
14692                | "optional_parameter_declaration"
14693                | "function_definition"
14694                | "type_definition"
14695                | "alias_declaration"
14696                | "template_instantiation"
14697                | "class_specifier"
14698                | "struct_specifier"
14699                | "union_specifier"
14700                | "enum_specifier"
14701        ) {
14702            return false;
14703        }
14704        current = ancestor.parent();
14705    }
14706    false
14707}
14708
14709/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
14710/// declaration's declarator inside a malformed class body.
14711///
14712/// An export macro between `class` and the class name can make the containing
14713/// body parse as a function body. A source declaration such as
14714/// `friend class internal::Friend;` then retains this exact structure:
14715/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
14716/// The declarator is a type reference despite its field role.
14717pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
14718    if !matches!(
14719        node.kind(),
14720        "qualified_identifier" | "scoped_type_identifier"
14721    ) {
14722        return false;
14723    }
14724    let Some(declaration) = node
14725        .parent()
14726        .filter(|parent| parent.kind() == "declaration")
14727    else {
14728        return false;
14729    };
14730    if declaration.child_by_field_name("declarator") != Some(node)
14731        || !declaration
14732            .child_by_field_name("type")
14733            .is_some_and(|friend| {
14734                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
14735            })
14736    {
14737        return false;
14738    }
14739    let mut cursor = declaration.walk();
14740    let mut errors = declaration
14741        .named_children(&mut cursor)
14742        .filter(|child| child.kind() == "ERROR");
14743    let Some(error) = errors.next() else {
14744        return false;
14745    };
14746    errors.next().is_none()
14747        && error.named_child_count() == 1
14748        && error.named_child(0).is_some_and(|class| {
14749            class.kind() == "identifier" && node_text(class, source) == "class"
14750        })
14751}
14752
14753pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
14754    if !matches!(node.kind(), "identifier" | "field_identifier") {
14755        return false;
14756    }
14757    if let Some(parent) = node.parent() {
14758        if parent.kind() == "call_expression"
14759            && parent.child_by_field_name("function") == Some(node)
14760        {
14761            return false;
14762        }
14763        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
14764            && parent.child_by_field_name("label") == Some(node)
14765        {
14766            return false;
14767        }
14768    }
14769    if is_declaration_name(node) {
14770        return false;
14771    }
14772    let mut current = node.parent();
14773    while let Some(ancestor) = current {
14774        match ancestor.kind() {
14775            "preproc_ifdef" | "preproc_ifndef" => {
14776                if ancestor
14777                    .child_by_field_name("name")
14778                    .is_some_and(|name| node_range_contains(name, node))
14779                {
14780                    return false;
14781                }
14782            }
14783            "preproc_if" | "preproc_elif" => {
14784                if ancestor
14785                    .child_by_field_name("condition")
14786                    .is_some_and(|condition| node_range_contains(condition, node))
14787                {
14788                    return false;
14789                }
14790            }
14791            "preproc_else" => {}
14792            kind if kind.starts_with("preproc_") => return false,
14793            _ => {}
14794        }
14795        if matches!(
14796            ancestor.kind(),
14797            "translation_unit" | "function_definition" | "compound_statement"
14798        ) {
14799            break;
14800        }
14801        current = ancestor.parent();
14802    }
14803    true
14804}
14805
14806fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
14807    outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
14808}
14809
14810fn recovered_c_reference_node(
14811    visibility: &VisibilityIndex<'_>,
14812    file: &ProjectFile,
14813    node: Node<'_>,
14814    source: &str,
14815) -> bool {
14816    if node.start_byte() >= node.end_byte()
14817        || node.is_error()
14818        || node.is_missing()
14819        || !matches!(
14820            node.kind(),
14821            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
14822        )
14823        || recovered_c_macro_binding_role(node)
14824        || recovered_c_label_role(node)
14825    {
14826        return false;
14827    }
14828    // A declaration name can be an identifier child of a recovered ERROR
14829    // (for example `EFI_STATUS Encode()` in C files parsed as C++). Do not
14830    // let a same-named macro turn that binder into a reference. An
14831    // assignment whose C callee follows the recovered `explicit` token is
14832    // the one expression-shaped exception: the generic declarator walk sees
14833    // its function_declarator as a declaration path, but the ERROR sibling
14834    // proves it is a call.
14835    let name = node_text(node, source);
14836    let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
14837    let recovered_macro_call = recovered_c_function_declarator_invocation(node)
14838        && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
14839    let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
14840    if is_declaration_name(node)
14841        && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
14842        && !recovered_function_call
14843        && !recovered_macro_call
14844        && !recovered_parenthesized_reference
14845    {
14846        return false;
14847    }
14848
14849    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
14850        return true;
14851    }
14852    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
14853        return true;
14854    }
14855    if recovered_parenthesized_reference {
14856        return true;
14857    }
14858    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
14859        if recovered_function_call {
14860            return true;
14861        }
14862        return visibility
14863            .visible_identifier_candidates(file, name)
14864            .any(|candidate| {
14865                candidate.is_class() || candidate.is_module() || is_type_alias(candidate)
14866            });
14867    }
14868    let visible = visibility
14869        .visible_identifier_candidates(file, name)
14870        .next()
14871        .is_some();
14872    visible
14873        && (recovered_c_reference_anchor(node)
14874            || recovered_c_error_expression_leaf(node)
14875            || recovered_function_call)
14876}
14877
14878fn push_recovered_c_range(
14879    ranges: &mut Vec<Range>,
14880    seen: &mut HashSet<(usize, usize)>,
14881    start_byte: usize,
14882    end_byte: usize,
14883    node: Node<'_>,
14884    limit: usize,
14885) -> bool {
14886    if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
14887        return true;
14888    }
14889    if ranges.len() >= limit {
14890        return false;
14891    }
14892    ranges.push(Range {
14893        start_byte,
14894        end_byte,
14895        start_line: node.start_position().row,
14896        end_line: node.end_position().row,
14897    });
14898    true
14899}
14900
14901/// A reference leaf can sit directly beneath an ERROR while its ERROR parent
14902/// is still attached to a real expression (most often a recovered macro call
14903/// argument). The expression parent is the structured proof; an unindexed
14904/// identifier beneath a bare recovery envelope has no such proof.
14905fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
14906    let mut current = node.parent();
14907    while let Some(parent) = current {
14908        if parent.is_error() {
14909            let Some(anchor) = parent.parent() else {
14910                return false;
14911            };
14912            return anchor.kind().ends_with("_expression")
14913                || matches!(
14914                    anchor.kind(),
14915                    "argument_list"
14916                        | "return_statement"
14917                        | "expression_statement"
14918                        | "case_statement"
14919                        | "initializer_list"
14920                        | "field_designator"
14921                        | "enumerator"
14922                );
14923        }
14924        if matches!(
14925            parent.kind(),
14926            "translation_unit" | "function_definition" | "compound_statement"
14927        ) {
14928            return false;
14929        }
14930        current = parent.parent();
14931    }
14932    false
14933}
14934
14935/// C recovery may represent a call as `identifier > function_declarator >
14936/// function_declarator > ERROR > compound_statement`. This shape is only a
14937/// call when the malformed declarator is attached to a real function body and
14938/// the name is an indexed visible callable. A declaration's
14939/// `ERROR > function_declarator > declaration` shape deliberately fails this
14940/// test.
14941fn recovered_c_function_call(
14942    visibility: &VisibilityIndex<'_>,
14943    file: &ProjectFile,
14944    node: Node<'_>,
14945    name: &str,
14946) -> bool {
14947    if !matches!(
14948        node.kind(),
14949        "identifier" | "field_identifier" | "type_identifier"
14950    ) {
14951        return false;
14952    }
14953    // C++ keywords used as C arguments can leave only the call prefix under
14954    // ERROR, with the remaining arguments in a following expression statement.
14955    // Require a statement boundary in a real block, an opening parenthesis,
14956    // and a recovered argument. A bare ERROR identifier supplies no call role.
14957    let error_call_prefix = node.parent().is_some_and(|error| {
14958        error.is_error()
14959            && error
14960                .parent()
14961                .is_some_and(|parent| parent.kind() == "compound_statement")
14962    }) && node
14963        .prev_sibling()
14964        .is_none_or(|previous| previous.kind() == ";")
14965        && node.next_sibling().is_some_and(|open| {
14966            open.kind() == "("
14967                && open.next_named_sibling().is_some_and(|argument| {
14968                    argument.kind() == "parameter_declaration" && !argument.has_error()
14969                })
14970        });
14971    (error_call_prefix || recovered_c_function_declarator_invocation(node))
14972        && visibility
14973            .visible_identifier_candidates(file, name)
14974            .any(CodeUnit::is_function)
14975}
14976
14977/// Return whether a C identifier belongs to a call-shaped declarator that the
14978/// C++ grammar put under an `ERROR` node.
14979///
14980/// The malformed call can be direct (`f(arg)`) or nested in a parameter
14981/// declaration when one of its arguments looks like a type (`f(TYPE, value)`).
14982/// In both cases the CST retains the function-declarator and its enclosing
14983/// recovery envelope. We walk only those declarator/parameter nodes and stop
14984/// at a real expression-bearing boundary; declarations therefore cannot pass
14985/// this predicate merely because they have a parameter list.
14986fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
14987    let mut function_declarator = if node.parent().is_some_and(|parent| {
14988        parent.kind() == "function_declarator"
14989            && parent.child_by_field_name("declarator") == Some(node)
14990    }) {
14991        node.parent().expect("checked function declarator parent")
14992    } else {
14993        let Some(parameter) = node.parent().filter(|parent| {
14994            parent.kind() == "parameter_declaration"
14995                && parent.child_by_field_name("type") == Some(node)
14996        }) else {
14997            return false;
14998        };
14999        if !parameter
15000            .child_by_field_name("declarator")
15001            .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15002        {
15003            return false;
15004        }
15005        let Some(parameters) = parameter
15006            .parent()
15007            .filter(|parent| parent.kind() == "parameter_list")
15008        else {
15009            return false;
15010        };
15011        let Some(function_declarator) = parameters
15012            .parent()
15013            .filter(|parent| parent.kind() == "function_declarator")
15014        else {
15015            return false;
15016        };
15017        function_declarator
15018    };
15019
15020    // Recovery may absorb the next statement's parenthesized cast into a
15021    // second function declarator. Follow only the original declarator chain.
15022    while let Some(parent) = function_declarator.parent().filter(|parent| {
15023        parent.kind() == "function_declarator"
15024            && parent.child_by_field_name("declarator") == Some(function_declarator)
15025    }) {
15026        function_declarator = parent;
15027    }
15028    let Some(mut current) = function_declarator
15029        .parent()
15030        .filter(|parent| parent.is_error())
15031    else {
15032        return false;
15033    };
15034    loop {
15035        let Some(parent) = current.parent() else {
15036            return false;
15037        };
15038        if matches!(
15039            parent.kind(),
15040            "translation_unit"
15041                | "compound_statement"
15042                | "preproc_if"
15043                | "preproc_ifdef"
15044                | "preproc_ifndef"
15045                | "preproc_else"
15046                | "preproc_elif"
15047        ) {
15048            return true;
15049        }
15050        if parent.kind() == "function_definition"
15051            && parent.child_by_field_name("declarator") == Some(current)
15052            && parent.named_child(0) == Some(current)
15053            && parent.child_by_field_name("body").is_some()
15054        {
15055            return true;
15056        }
15057        if parent.is_error()
15058            || matches!(
15059                parent.kind(),
15060                "parameter_declaration"
15061                    | "parameter_list"
15062                    | "function_declarator"
15063                    | "abstract_function_declarator"
15064                    | "parenthesized_declarator"
15065            )
15066        {
15067            current = parent;
15068            continue;
15069        }
15070        return false;
15071    }
15072}
15073
15074/// C permits an identifier named `typename`. The C++ grammar can recover an
15075/// assignment using that identifier as a declaration whose declarator is a
15076/// parenthesized argument list, for example `typename = f(ctx, value)`. Only
15077/// the argument retained beneath the nested `ERROR` is a reference; sibling
15078/// declarator identifiers remain binders/grammar artifacts.
15079fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
15080    let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
15081        return false;
15082    };
15083    if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
15084        return false;
15085    }
15086    let Some(declarator) = error
15087        .parent()
15088        .filter(|parent| parent.kind() == "parenthesized_declarator")
15089    else {
15090        return false;
15091    };
15092    let Some(declaration) = declarator
15093        .parent()
15094        .filter(|parent| parent.kind() == "declaration")
15095    else {
15096        return false;
15097    };
15098    if declaration.child_by_field_name("declarator") != Some(declarator) {
15099        return false;
15100    }
15101    let Some(type_node) = declaration.child_by_field_name("type") else {
15102        return false;
15103    };
15104    type_node.kind() == "dependent_type"
15105        && type_node
15106            .child(0)
15107            .is_some_and(|keyword| keyword.kind() == "typename")
15108}
15109
15110fn recovered_c_explicit_assignment_callee(
15111    visibility: &VisibilityIndex<'_>,
15112    file: &ProjectFile,
15113    node: Node<'_>,
15114    name: &str,
15115) -> bool {
15116    let mut current = node;
15117    let error = loop {
15118        let Some(parent) = current.parent() else {
15119            return false;
15120        };
15121        if parent.is_error() {
15122            break parent;
15123        }
15124        current = parent;
15125    };
15126    let mut cursor = error.walk();
15127    let explicit_recovery_precedes_callee = error
15128        .named_children(&mut cursor)
15129        .take_while(|child| child.start_byte() < node.start_byte())
15130        .any(|child| child.kind() == "explicit_function_specifier");
15131    if !explicit_recovery_precedes_callee {
15132        return false;
15133    }
15134    visibility
15135        .visible_identifier_candidates(file, name)
15136        .any(CodeUnit::is_function)
15137}
15138
15139fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
15140    while let Some(parent) = node.parent() {
15141        if matches!(
15142            parent.kind(),
15143            "preproc_def" | "preproc_function_def" | "preproc_params"
15144        ) {
15145            return true;
15146        }
15147        if parent.is_error()
15148            || matches!(
15149                parent.kind(),
15150                "translation_unit" | "function_definition" | "compound_statement"
15151            )
15152        {
15153            return false;
15154        }
15155        node = parent;
15156    }
15157    false
15158}
15159
15160fn recovered_c_label_role(node: Node<'_>) -> bool {
15161    node.parent().is_some_and(|parent| {
15162        matches!(parent.kind(), "labeled_statement" | "goto_statement")
15163            && parent.child_by_field_name("label") == Some(node)
15164    })
15165}
15166
15167fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
15168    while let Some(parent) = node.parent() {
15169        if parent.is_error() {
15170            return false;
15171        }
15172        // A C macro call recovered as a function declarator can parse an
15173        // assignment-shaped argument as an optional parameter. Its
15174        // `default_value` field remains an expression role even though the
15175        // surrounding call shape is beneath ERROR.
15176        if parent.kind() == "optional_parameter_declaration"
15177            && parent
15178                .child_by_field_name("default_value")
15179                .is_some_and(|value| node_range_contains(value, node))
15180        {
15181            return true;
15182        }
15183        if parent.kind().ends_with("_expression")
15184            || matches!(
15185                parent.kind(),
15186                "argument_list"
15187                    | "return_statement"
15188                    | "expression_statement"
15189                    | "case_statement"
15190                    | "initializer_list"
15191                    | "init_declarator"
15192                    | "array_declarator"
15193                    | "field_designator"
15194                    | "enumerator"
15195            )
15196        {
15197            return true;
15198        }
15199        if matches!(
15200            parent.kind(),
15201            "translation_unit"
15202                | "function_definition"
15203                | "compound_statement"
15204                | "declaration"
15205                | "field_declaration"
15206                | "parameter_declaration"
15207        ) {
15208            return false;
15209        }
15210        node = parent;
15211    }
15212    false
15213}
15214
15215/// Whether a parameter declaration belongs to the callable scope whose body can
15216/// contain references to it.
15217///
15218/// Error recovery can wrap a macro-decorated class body in a synthetic outer
15219/// `function_definition`. Merely finding any callable ancestor would then leak
15220/// parameters from member prototypes into later member bodies. Require the
15221/// parameter to be inside that definition's own declarator instead.
15222pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
15223    let mut current = parameter.parent();
15224    while let Some(ancestor) = current {
15225        if ancestor.kind() == "lambda_expression" {
15226            return ancestor
15227                .child_by_field_name("declarator")
15228                .is_some_and(|declarator| {
15229                    declarator.start_byte() <= parameter.start_byte()
15230                        && parameter.end_byte() <= declarator.end_byte()
15231                });
15232        }
15233        if ancestor.kind() == "function_definition" {
15234            return ancestor
15235                .child_by_field_name("declarator")
15236                .is_some_and(|declarator| {
15237                    declarator.start_byte() <= parameter.start_byte()
15238                        && parameter.end_byte() <= declarator.end_byte()
15239                });
15240        }
15241        current = ancestor.parent();
15242    }
15243    false
15244}
15245
15246pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
15247    let mut current = node.parent();
15248    while let Some(ancestor) = current {
15249        if matches!(
15250            ancestor.kind(),
15251            "parameter_declaration" | "optional_parameter_declaration"
15252        ) {
15253            return ancestor
15254                .child_by_field_name("type")
15255                .is_some_and(|type_node| {
15256                    type_node.start_byte() <= node.start_byte()
15257                        && node.end_byte() <= type_node.end_byte()
15258                });
15259        }
15260        if matches!(
15261            ancestor.kind(),
15262            "function_definition" | "lambda_expression" | "compound_statement"
15263        ) {
15264            return false;
15265        }
15266        current = ancestor.parent();
15267    }
15268    false
15269}
15270
15271fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
15272    if specifier.child_by_field_name("body").is_some() {
15273        return true;
15274    }
15275    let mut current = specifier.parent();
15276    while let Some(ancestor) = current {
15277        match ancestor.kind() {
15278            "type_descriptor"
15279            | "parameter_declaration"
15280            | "optional_parameter_declaration"
15281            | "template_argument_list"
15282            | "cast_expression" => return false,
15283            "declaration" | "field_declaration" => {
15284                let mut cursor = ancestor.walk();
15285                return ancestor
15286                    .children_by_field_name("declarator", &mut cursor)
15287                    .next()
15288                    .is_none();
15289            }
15290            "translation_unit" => return true,
15291            _ => current = ancestor.parent(),
15292        }
15293    }
15294    false
15295}
15296
15297pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
15298    match node.kind() {
15299        "identifier"
15300        | "field_identifier"
15301        | "qualified_identifier"
15302        | "scoped_identifier"
15303        | "operator_name"
15304        | "destructor_name"
15305        | "literal_operator_name" => Some(node),
15306        "reference_declarator" | "parenthesized_declarator" => {
15307            node.named_child(0).and_then(declarator_name_node)
15308        }
15309        _ => node
15310            .child_by_field_name("declarator")
15311            .or_else(|| node.child_by_field_name("name"))
15312            .or_else(|| node.child_by_field_name("field"))
15313            .and_then(declarator_name_node),
15314    }
15315}
15316
15317fn declarator_name_path_contains(
15318    declarator: Node<'_>,
15319    candidate: Node<'_>,
15320    allow_type_identifier: bool,
15321) -> bool {
15322    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
15323        return false;
15324    };
15325    let mut current = Some(declarator);
15326    while let Some(node) = current {
15327        if same_node(node, candidate) {
15328            return true;
15329        }
15330        if same_node(node, name) {
15331            return false;
15332        }
15333        current = node
15334            .child_by_field_name("declarator")
15335            .or_else(|| node.child_by_field_name("name"))
15336            .or_else(|| node.child_by_field_name("field"));
15337    }
15338    false
15339}
15340
15341fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
15342    match node.kind() {
15343        "identifier"
15344        | "field_identifier"
15345        | "operator_name"
15346        | "destructor_name"
15347        | "literal_operator_name" => Some(node),
15348        "type_identifier" if allow_type_identifier => Some(node),
15349        _ => node
15350            .child_by_field_name("declarator")
15351            .or_else(|| node.child_by_field_name("name"))
15352            .or_else(|| node.child_by_field_name("field"))
15353            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
15354    }
15355}
15356
15357/// True when `node` is a component of a larger structured type node whose outer
15358/// range is the single reference surfaced to callers.
15359pub fn is_nested_type_node(node: Node<'_>) -> bool {
15360    node.parent().is_some_and(|parent| {
15361        matches!(
15362            parent.kind(),
15363            "qualified_identifier" | "scoped_type_identifier" | "template_type"
15364        )
15365    })
15366}
15367
15368pub struct OutOfLineMemberDefinitionOwners<'tree> {
15369    pub owners: Vec<(Node<'tree>, CodeUnit)>,
15370    innermost: Option<(Node<'tree>, CodeUnit)>,
15371}
15372
15373impl OutOfLineMemberDefinitionOwners<'_> {
15374    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
15375        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
15376    }
15377}
15378
15379pub struct QualifiedOwnerComponents<'tree> {
15380    pub nodes: Vec<Node<'tree>>,
15381    pub names: Vec<String>,
15382    pub global: bool,
15383}
15384
15385/// True when each structured qualifier on the callable-name path has a real
15386/// `::` token. A macro-prefixed return type can make tree-sitter insert a
15387/// zero-width missing separator and parse `TYPE Result<T> method()` as the
15388/// false qualified declarator `Result<T>::method`.
15389pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
15390    let mut stack = vec![node];
15391    let mut found_separator = false;
15392    while let Some(current) = stack.pop() {
15393        if !matches!(
15394            current.kind(),
15395            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15396        ) {
15397            continue;
15398        }
15399        let mut current_has_separator = false;
15400        for child in children_iter(current) {
15401            if child.kind() == "::" {
15402                if child.is_missing() {
15403                    return false;
15404                }
15405                current_has_separator = true;
15406                found_separator = true;
15407            }
15408        }
15409        if !current_has_separator {
15410            return false;
15411        }
15412        for field in ["scope", "name"] {
15413            if let Some(child) = current.child_by_field_name(field)
15414                && matches!(
15415                    child.kind(),
15416                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
15417                )
15418            {
15419                stack.push(child);
15420            }
15421        }
15422    }
15423    found_separator
15424}
15425
15426pub fn qualified_owner_components<'tree>(
15427    node: Node<'tree>,
15428    source: &str,
15429) -> Option<QualifiedOwnerComponents<'tree>> {
15430    if !qualified_name_has_concrete_scope_separators(node) {
15431        return None;
15432    }
15433    let mut nodes = cpp_name_component_nodes(node)?;
15434    nodes.pop()?;
15435    if nodes.is_empty() {
15436        return None;
15437    }
15438    let names = nodes
15439        .iter()
15440        .map(|component| node_text(*component, source).to_string())
15441        .collect();
15442    Some(QualifiedOwnerComponents {
15443        nodes,
15444        names,
15445        global: is_globally_qualified_cpp_name(node),
15446    })
15447}
15448
15449pub fn out_of_line_member_definition_owner<'tree>(
15450    analyzer: &CppGraphSource<'_>,
15451    visibility: &VisibilityIndex<'_>,
15452    file: &ProjectFile,
15453    source: &str,
15454    node: Node<'tree>,
15455) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
15456    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
15457        || !has_ancestor_kind(node, "function_definition")
15458        || !is_function_declarator_name_root(node)
15459    {
15460        return None;
15461    }
15462    let qualified = qualified_owner_components(node, source)?;
15463    let lexical_scope = enclosing_namespace_components(node, source)?;
15464    let mut owners = Vec::new();
15465    let mut innermost = None;
15466
15467    for component_count in 1..=qualified.names.len() {
15468        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
15469            .resolve_type_components_lexically(
15470                analyzer,
15471                file,
15472                &qualified.names[..component_count],
15473                qualified.global,
15474                &lexical_scope,
15475            )
15476            && !owners
15477                .iter()
15478                .any(|(_, existing)| same_visible_symbol(existing, &unit))
15479        {
15480            if component_count == qualified.names.len() {
15481                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
15482            }
15483            owners.push((qualified.nodes[component_count - 1], unit));
15484        }
15485    }
15486
15487    // The C++ analyzer has already reconciled an indexed out-of-line callable
15488    // against the include-visible class table. Consult that canonical owner
15489    // chain only when ordinary lexical lookup could not recover the innermost
15490    // owner.  A one-segment qualifier is safe here only when the enclosing
15491    // indexed callable has an authoritative class owner and the parser's
15492    // namespace path is a (possibly sparse) subsequence of that owner path.
15493    // The latter is what lets macro-wrapped namespace sentinels recover a
15494    // missing `time_internal`/`cord_internal` component without guessing an
15495    // unrelated short name.
15496    if innermost.is_none() {
15497        let indexed_owner_components = visibility
15498            .indexed_enclosing_owner_scope(analyzer, file, node)
15499            .or_else(|| {
15500                // Retain the legacy rendered-name fallback for the existing
15501                // multi-segment path when an enclosing owner chain is not
15502                // available (for example, cache-loaded units without parent
15503                // links).  One-segment recovery must stay canonical-only.
15504                if qualified.names.len() <= 1 {
15505                    return None;
15506                }
15507                let range = Range {
15508                    start_byte: node.start_byte(),
15509                    end_byte: node.end_byte(),
15510                    start_line: node.start_position().row,
15511                    end_line: node.end_position().row,
15512                };
15513                let start = analyzer.enclosing_code_unit(file, &range)?;
15514                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
15515                    brokk_bifrost_core::analyzer::Language::Cpp,
15516                    &cpp_name_for(&start),
15517                );
15518                components.pop();
15519                Some(components)
15520            });
15521        if let Some(indexed_owner_components) = indexed_owner_components
15522            && indexed_owner_components.len() > qualified.names.len()
15523            && indexed_owner_components.ends_with(&qualified.names)
15524            && indexed_namespace_path_is_recoverable(
15525                &lexical_scope,
15526                &indexed_owner_components,
15527                qualified.names.len(),
15528            )
15529            // A globally-qualified one-segment owner is an explicit request
15530            // for the top-level binding; do not reinterpret it as a missing
15531            // namespace component.  Existing multi-segment global lookups
15532            // retain their historical indexed recovery.
15533            && (qualified.names.len() > 1 || !qualified.global)
15534        {
15535            let namespace_count = indexed_owner_components.len() - qualified.names.len();
15536            for component_count in 1..=qualified.names.len() {
15537                let expected = &indexed_owner_components[..namespace_count + component_count];
15538                let owner_node = qualified.nodes[component_count - 1];
15539                for owner in visibility
15540                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
15541                    .filter(|candidate| candidate.is_class())
15542                    .filter(|candidate| {
15543                        canonical_cpp_scope_components(candidate) == expected
15544                            && visibility.external_type_candidate_visible_in_context(
15545                                analyzer, file, candidate, node,
15546                            )
15547                    })
15548                {
15549                    if component_count == qualified.names.len() && innermost.is_none() {
15550                        innermost = Some((owner_node, owner.clone()));
15551                    }
15552                    if !owners
15553                        .iter()
15554                        .any(|(_, existing)| same_symbol(existing, owner))
15555                    {
15556                        owners.push((owner_node, owner.clone()));
15557                    }
15558                }
15559            }
15560        }
15561    }
15562    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
15563}
15564
15565fn is_function_declarator_name_root(node: Node<'_>) -> bool {
15566    let mut current = node;
15567    while let Some(parent) = current.parent() {
15568        if parent.kind() == "function_declarator" {
15569            return parent.child_by_field_name("declarator") == Some(current);
15570        }
15571        if matches!(
15572            parent.kind(),
15573            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
15574        ) && parent.child_by_field_name("declarator") == Some(current)
15575        {
15576            current = parent;
15577            continue;
15578        }
15579        return false;
15580    }
15581    false
15582}
15583
15584pub fn append_cpp_name_components(
15585    node: Node<'_>,
15586    source: &str,
15587    out: &mut Vec<String>,
15588) -> Option<()> {
15589    out.extend(
15590        cpp_name_component_nodes(node)?
15591            .into_iter()
15592            .map(|component| node_text(component, source).to_string()),
15593    );
15594    Some(())
15595}
15596
15597pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
15598    let mut components = Vec::new();
15599    append_cpp_name_components(node, source, &mut components)?;
15600    Some(components)
15601}
15602
15603/// Resolve a structured type spelling from an object-like macro replacement
15604/// when definition-site source order has no answer.
15605///
15606/// Macro replacement tokens are looked up where the macro is expanded, so a
15607/// type declared later in the defining header can still be their destination.
15608/// Without expanding every invocation, accept only one include-visible logical
15609/// class or alias whose structured path ends in the replacement components.
15610/// An ordinary lexical answer always takes precedence at the call site.
15611pub fn unique_macro_replacement_type_candidate(
15612    analyzer: &CppGraphSource<'_>,
15613    visibility: &VisibilityIndex<'_>,
15614    file: &ProjectFile,
15615    components: &[String],
15616) -> Option<CodeUnit> {
15617    let terminal = components.last()?;
15618    let mut candidates = Vec::new();
15619    for candidate in visibility
15620        .visible_identifier_candidates(file, terminal)
15621        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
15622        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
15623    {
15624        if !candidates
15625            .iter()
15626            .any(|existing| same_logical_symbol(existing, candidate))
15627        {
15628            candidates.push(candidate.clone());
15629        }
15630    }
15631    (candidates.len() == 1).then(|| candidates.remove(0))
15632}
15633
15634/// The base scopes named by member using-declarations for `member` in one
15635/// class source range.
15636///
15637/// The grammar supplies the qualified identifier and each component. Keep
15638/// this interpretation shared between forward overload lookup and inverse
15639/// owner routing rather than reparsing a rendered `Base::member` string at
15640/// either call site.
15641pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
15642    let mut parser = Parser::new();
15643    if parser
15644        .set_language(&tree_sitter_cpp::LANGUAGE.into())
15645        .is_err()
15646    {
15647        return Vec::new();
15648    }
15649    let Some(tree) = parser.parse(source, None) else {
15650        return Vec::new();
15651    };
15652    let mut scopes = Vec::new();
15653    let mut pending = vec![tree.root_node()];
15654    while let Some(node) = pending.pop() {
15655        if node.kind() == "using_declaration" {
15656            let Some(imported) = node.named_child(0) else {
15657                continue;
15658            };
15659            let Some(mut components) = cpp_type_name_components(imported, source) else {
15660                continue;
15661            };
15662            if components.pop().as_deref() == Some(member) && !components.is_empty() {
15663                scopes.push(components.join("::"));
15664            }
15665            continue;
15666        }
15667        push_named_children_reversed(node, &mut pending);
15668    }
15669    scopes
15670}
15671
15672/// Whether a structured using-declaration scope can name `qualified` as an
15673/// ancestor class. The boundary check prevents `Base` from matching
15674/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
15675pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
15676    qualified == scope
15677        || qualified
15678            .strip_suffix(scope)
15679            .is_some_and(|prefix| prefix.ends_with("::"))
15680}
15681
15682/// Whether `node` is the direct structured type payload of a template
15683/// argument. This role remains meaningful even when a surrounding expression
15684/// is below tree-sitter recovery, because both the `template_argument_list`
15685/// and the `type_descriptor` retain their named fields.
15686pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
15687    let Some(type_descriptor) = node.parent() else {
15688        return false;
15689    };
15690    if type_descriptor.kind() != "type_descriptor"
15691        || type_descriptor.child_by_field_name("type") != Some(node)
15692    {
15693        return false;
15694    }
15695    let Some(arguments) = type_descriptor.parent() else {
15696        return false;
15697    };
15698    if arguments.kind() != "template_argument_list" {
15699        return false;
15700    }
15701    arguments.parent().is_some_and(|parent| {
15702        matches!(parent.kind(), "template_type" | "template_function")
15703            && parent.child_by_field_name("arguments") == Some(arguments)
15704    })
15705}
15706
15707pub fn cpp_template_reference_arguments(
15708    mut node: Node<'_>,
15709    source: &str,
15710) -> Option<Vec<CppTemplateExpression>> {
15711    loop {
15712        match node.kind() {
15713            "template_type" | "template_function" => {
15714                let arguments = node.child_by_field_name("arguments")?;
15715                let mut cursor = arguments.walk();
15716                return Some(
15717                    arguments
15718                        .named_children(&mut cursor)
15719                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
15720                        .map(|argument| CppTemplateExpression {
15721                            text: normalize_cpp_whitespace(node_text(argument, source)),
15722                            // One template term from a resolver query; see `ParentIndex::unindexed`.
15723                            term: cpp_template_term(
15724                                argument,
15725                                source,
15726                                &[],
15727                                &ParentIndex::unindexed(),
15728                            ),
15729                        })
15730                        .collect(),
15731                );
15732            }
15733            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
15734                node = node
15735                    .child_by_field_name("name")
15736                    .or_else(|| node.child_by_field_name("type"))?;
15737            }
15738            _ => return None,
15739        }
15740    }
15741}
15742
15743fn cpp_reconcile_primary_template_parameters(
15744    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
15745    preferred: &CodeUnit,
15746) -> Option<Vec<CppTemplateParameterMetadata>> {
15747    let canonical = candidates
15748        .iter()
15749        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
15750    let mut merged = canonical
15751        .parameters
15752        .iter()
15753        .map(|parameter| CppTemplateParameterMetadata {
15754            name: parameter.name.clone(),
15755            kind: parameter.kind,
15756            variadic: parameter.variadic,
15757            default: None,
15758        })
15759        .collect::<Vec<_>>();
15760
15761    for (_, metadata) in candidates {
15762        if metadata.parameters.len() != merged.len() {
15763            return None;
15764        }
15765        let rename_bindings = metadata
15766            .parameters
15767            .iter()
15768            .zip(&merged)
15769            .map(|(parameter, canonical)| {
15770                (
15771                    parameter.name.clone(),
15772                    CppTemplateTerm::Parameter(canonical.name.clone()),
15773                )
15774            })
15775            .collect::<HashMap<_, _>>();
15776        for ((parameter, canonical), merged_parameter) in metadata
15777            .parameters
15778            .iter()
15779            .zip(&canonical.parameters)
15780            .zip(&mut merged)
15781        {
15782            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
15783                return None;
15784            }
15785            let Some(default) = &parameter.default else {
15786                continue;
15787            };
15788            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
15789            if let Some(existing) = &merged_parameter.default {
15790                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
15791                    return None;
15792                }
15793            } else {
15794                merged_parameter.default = Some(CppTemplateExpression {
15795                    text: default.text.clone(),
15796                    term: normalized_term,
15797                });
15798            }
15799        }
15800    }
15801    Some(merged)
15802}
15803
15804pub fn cpp_bind_template_arguments(
15805    parameters: &[CppTemplateParameterMetadata],
15806    explicit_arguments: &[CppTemplateExpression],
15807) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
15808    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
15809    if variadic_index.is_some_and(|index| {
15810        index + 1 != parameters.len()
15811            || parameters[index + 1..]
15812                .iter()
15813                .any(|parameter| parameter.variadic)
15814    }) {
15815        return None;
15816    }
15817    let fixed_count = variadic_index.unwrap_or(parameters.len());
15818    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
15819        return None;
15820    }
15821    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
15822    let mut expanded = explicit_arguments[..explicit_fixed_count]
15823        .iter()
15824        .map(cpp_clone_template_expression_iterative)
15825        .collect::<Vec<_>>();
15826    let mut bindings = HashMap::default();
15827    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
15828        bindings.insert(
15829            parameter.name.clone(),
15830            cpp_clone_template_term_iterative(&argument.term),
15831        );
15832    }
15833    for parameter in &parameters[explicit_fixed_count..fixed_count] {
15834        let default = parameter.default.as_ref()?;
15835        let term = cpp_substitute_template_term(&default.term, &bindings)?;
15836        bindings.insert(parameter.name.clone(), term.clone());
15837        expanded.push(CppTemplateExpression {
15838            text: default.text.clone(),
15839            term,
15840        });
15841    }
15842    if let Some(index) = variadic_index {
15843        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
15844        expanded.extend(
15845            packed_arguments
15846                .iter()
15847                .map(cpp_clone_template_expression_iterative),
15848        );
15849        bindings.insert(
15850            parameters[index].name.clone(),
15851            CppTemplateTerm::Node {
15852                kind: "parameter_pack".to_string(),
15853                children: packed_arguments
15854                    .iter()
15855                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
15856                    .collect(),
15857            },
15858        );
15859    }
15860    Some((expanded, bindings))
15861}
15862
15863fn cpp_specialization_matches(
15864    metadata: &CppTemplateMetadata,
15865    arguments: &[CppTemplateExpression],
15866) -> bool {
15867    if metadata.specialization_arguments.len() != arguments.len() {
15868        return false;
15869    }
15870    let parameter_names = metadata
15871        .parameters
15872        .iter()
15873        .map(|parameter| parameter.name.as_str())
15874        .collect::<HashSet<_>>();
15875    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
15876    for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
15877        if !cpp_unify_template_term(
15878            &pattern.term,
15879            &argument.term,
15880            &parameter_names,
15881            &mut bindings,
15882        ) {
15883            return false;
15884        }
15885    }
15886    true
15887}
15888
15889fn cpp_specialization_more_specialized(
15890    candidate: &CppTemplateMetadata,
15891    other: &CppTemplateMetadata,
15892) -> bool {
15893    cpp_specialization_pattern_accepts(other, candidate)
15894        && !cpp_specialization_pattern_accepts(candidate, other)
15895}
15896
15897fn cpp_specialization_pattern_accepts(
15898    broader: &CppTemplateMetadata,
15899    narrower: &CppTemplateMetadata,
15900) -> bool {
15901    if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
15902        return false;
15903    }
15904    let parameter_names = broader
15905        .parameters
15906        .iter()
15907        .map(|parameter| parameter.name.as_str())
15908        .collect::<HashSet<_>>();
15909    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
15910    broader
15911        .specialization_arguments
15912        .iter()
15913        .zip(&narrower.specialization_arguments)
15914        .all(|(pattern, argument)| {
15915            cpp_unify_template_term(
15916                &pattern.term,
15917                &argument.term,
15918                &parameter_names,
15919                &mut bindings,
15920            )
15921        })
15922}
15923
15924pub fn cpp_substitute_template_term(
15925    term: &CppTemplateTerm,
15926    bindings: &HashMap<String, CppTemplateTerm>,
15927) -> Option<CppTemplateTerm> {
15928    enum Work<'a> {
15929        Visit(&'a CppTemplateTerm),
15930        Build { kind: String, child_count: usize },
15931    }
15932
15933    let mut work = vec![Work::Visit(term)];
15934    let mut substituted = Vec::new();
15935    while let Some(next) = work.pop() {
15936        match next {
15937            Work::Visit(CppTemplateTerm::Parameter(name)) => {
15938                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
15939            }
15940            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
15941                substituted.push(CppTemplateTerm::Atom {
15942                    kind: kind.clone(),
15943                    text: text.clone(),
15944                });
15945            }
15946            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
15947                work.push(Work::Build {
15948                    kind: kind.clone(),
15949                    child_count: children.len(),
15950                });
15951                work.extend(children.iter().rev().map(Work::Visit));
15952            }
15953            Work::Build { kind, child_count } => {
15954                let children = substituted.split_off(substituted.len() - child_count);
15955                substituted.push(CppTemplateTerm::Node { kind, children });
15956            }
15957        }
15958    }
15959    substituted.pop()
15960}
15961
15962pub fn cpp_substitute_template_arguments(
15963    arguments: &[CppTemplateExpression],
15964    bindings: &HashMap<String, CppTemplateTerm>,
15965) -> Option<Vec<CppTemplateExpression>> {
15966    let mut substituted = Vec::new();
15967    for argument in arguments {
15968        let CppTemplateTerm::Node { kind, children } = &argument.term else {
15969            substituted.push(CppTemplateExpression {
15970                text: argument.text.clone(),
15971                term: cpp_substitute_template_term(&argument.term, bindings)?,
15972            });
15973            continue;
15974        };
15975        if kind != "parameter_pack_expansion" {
15976            substituted.push(CppTemplateExpression {
15977                text: argument.text.clone(),
15978                term: cpp_substitute_template_term(&argument.term, bindings)?,
15979            });
15980            continue;
15981        }
15982        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
15983            return None;
15984        };
15985        if ellipsis != "..." {
15986            return None;
15987        }
15988
15989        let mut pack_names = Vec::new();
15990        let mut work = vec![pattern];
15991        while let Some(term) = work.pop() {
15992            match term {
15993                CppTemplateTerm::Parameter(name)
15994                    if matches!(
15995                        bindings.get(name),
15996                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
15997                    ) =>
15998                {
15999                    if !pack_names.contains(name) {
16000                        pack_names.push(name.clone());
16001                    }
16002                }
16003                CppTemplateTerm::Node { children, .. } => work.extend(children),
16004                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16005            }
16006        }
16007        let first_pack = pack_names.first()?;
16008        let CppTemplateTerm::Node {
16009            children: first_elements,
16010            ..
16011        } = bindings.get(first_pack)?
16012        else {
16013            return None;
16014        };
16015        let pack_len = first_elements.len();
16016        for pack_name in &pack_names {
16017            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16018                return None;
16019            };
16020            if children.len() != pack_len {
16021                return None;
16022            }
16023        }
16024        for index in 0..pack_len {
16025            let mut element_bindings = bindings.clone();
16026            for pack_name in &pack_names {
16027                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16028                    return None;
16029                };
16030                element_bindings.insert(
16031                    pack_name.clone(),
16032                    cpp_clone_template_term_iterative(&children[index]),
16033                );
16034            }
16035            substituted.push(CppTemplateExpression {
16036                text: argument.text.clone(),
16037                term: cpp_substitute_template_term(pattern, &element_bindings)?,
16038            });
16039        }
16040    }
16041    Some(substituted)
16042}
16043
16044fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
16045    enum Work<'a> {
16046        Visit(&'a CppTemplateTerm),
16047        Build { kind: String, child_count: usize },
16048    }
16049
16050    let mut work = vec![Work::Visit(term)];
16051    let mut cloned = Vec::new();
16052    while let Some(next) = work.pop() {
16053        match next {
16054            Work::Visit(CppTemplateTerm::Parameter(name)) => {
16055                cloned.push(CppTemplateTerm::Parameter(name.clone()));
16056            }
16057            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16058                cloned.push(CppTemplateTerm::Atom {
16059                    kind: kind.clone(),
16060                    text: text.clone(),
16061                });
16062            }
16063            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16064                work.push(Work::Build {
16065                    kind: kind.clone(),
16066                    child_count: children.len(),
16067                });
16068                work.extend(children.iter().rev().map(Work::Visit));
16069            }
16070            Work::Build { kind, child_count } => {
16071                let children = cloned.split_off(cloned.len() - child_count);
16072                cloned.push(CppTemplateTerm::Node { kind, children });
16073            }
16074        }
16075    }
16076    cloned
16077        .pop()
16078        .expect("template term traversal emits one root")
16079}
16080
16081fn cpp_clone_template_expression_iterative(
16082    expression: &CppTemplateExpression,
16083) -> CppTemplateExpression {
16084    CppTemplateExpression {
16085        text: expression.text.clone(),
16086        term: cpp_clone_template_term_iterative(&expression.term),
16087    }
16088}
16089
16090pub fn cpp_unify_template_term(
16091    pattern: &CppTemplateTerm,
16092    argument: &CppTemplateTerm,
16093    parameters: &HashSet<&str>,
16094    bindings: &mut HashMap<String, CppTemplateTerm>,
16095) -> bool {
16096    let mut work = vec![(pattern, argument)];
16097    while let Some((pattern, argument)) = work.pop() {
16098        match pattern {
16099            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
16100                if let Some(bound) = bindings.get(name) {
16101                    if !cpp_template_terms_equal(bound, argument) {
16102                        return false;
16103                    }
16104                } else {
16105                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
16106                }
16107            }
16108            CppTemplateTerm::Atom {
16109                kind: pattern_kind,
16110                text: pattern_text,
16111            } => {
16112                if !matches!(
16113                    argument,
16114                    CppTemplateTerm::Atom { kind, text }
16115                        if kind == pattern_kind && text == pattern_text
16116                ) {
16117                    return false;
16118                }
16119            }
16120            CppTemplateTerm::Node {
16121                kind: pattern_kind,
16122                children: pattern_children,
16123            } => {
16124                let CppTemplateTerm::Node { kind, children } = argument else {
16125                    return false;
16126                };
16127                if kind != pattern_kind || children.len() != pattern_children.len() {
16128                    return false;
16129                }
16130                work.extend(pattern_children.iter().zip(children).rev());
16131            }
16132            CppTemplateTerm::Parameter(_) => return false,
16133        }
16134    }
16135    true
16136}
16137
16138fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
16139    let mut work = vec![(left, right)];
16140    while let Some((left, right)) = work.pop() {
16141        match (left, right) {
16142            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
16143                if left != right {
16144                    return false;
16145                }
16146            }
16147            (
16148                CppTemplateTerm::Atom {
16149                    kind: left_kind,
16150                    text: left_text,
16151                },
16152                CppTemplateTerm::Atom {
16153                    kind: right_kind,
16154                    text: right_text,
16155                },
16156            ) => {
16157                if left_kind != right_kind || left_text != right_text {
16158                    return false;
16159                }
16160            }
16161            (
16162                CppTemplateTerm::Node {
16163                    kind: left_kind,
16164                    children: left_children,
16165                },
16166                CppTemplateTerm::Node {
16167                    kind: right_kind,
16168                    children: right_children,
16169                },
16170            ) => {
16171                if left_kind != right_kind || left_children.len() != right_children.len() {
16172                    return false;
16173                }
16174                work.extend(left_children.iter().zip(right_children).rev());
16175            }
16176            _ => return false,
16177        }
16178    }
16179    true
16180}
16181
16182pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
16183    let mut components = Vec::new();
16184    let mut stack = vec![node];
16185    while let Some(current) = stack.pop() {
16186        match current.kind() {
16187            "identifier"
16188            | "field_identifier"
16189            | "namespace_identifier"
16190            | "type_identifier"
16191            | "operator_name"
16192            | "destructor_name" => components.push(current),
16193            "template_type" | "template_function" => {
16194                stack.push(current.child_by_field_name("name")?);
16195            }
16196            "dependent_name" => stack.push(current.named_child(0)?),
16197            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
16198                stack.push(current.child_by_field_name("name")?);
16199                if let Some(scope) = current.child_by_field_name("scope") {
16200                    stack.push(scope);
16201                }
16202            }
16203            "nested_namespace_specifier" => {
16204                for index in (0..current.named_child_count()).rev() {
16205                    stack.push(current.named_child(index)?);
16206                }
16207            }
16208            _ => return None,
16209        }
16210    }
16211    Some(components)
16212}
16213
16214pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
16215    node.child_by_field_name("scope").is_none()
16216        && node.child(0).is_some_and(|child| child.kind() == "::")
16217}
16218
16219fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16220    let mut namespaces = Vec::new();
16221    let mut current = node.parent();
16222    while let Some(parent) = current {
16223        if parent.kind() == "namespace_definition"
16224            && let Some(name) = parent.child_by_field_name("name")
16225        {
16226            let mut components = Vec::new();
16227            append_cpp_name_components(name, source, &mut components)?;
16228            namespaces.push(components);
16229        }
16230        current = parent.parent();
16231    }
16232    namespaces.reverse();
16233    Some(namespaces.into_iter().flatten().collect())
16234}
16235
16236/// Whether a parser-derived namespace path can be reconciled with an indexed
16237/// owner scope without inventing an unrelated short-name binding.
16238///
16239/// Macro namespace sentinels can make tree-sitter omit one or more namespace
16240/// definitions from the ancestor chain. Preserve the order of every namespace
16241/// that did survive parsing, but allow indexed components between them. An
16242/// empty path is accepted only when the declarator itself supplies a nested
16243/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
16244/// owner chain, that suffix is structural evidence that a namespace was lost.
16245/// A one-segment owner at the translation-unit root remains insufficient.
16246fn indexed_namespace_path_is_recoverable(
16247    lexical_scope: &[String],
16248    indexed_owner_scope: &[String],
16249    explicit_owner_component_count: usize,
16250) -> bool {
16251    if lexical_scope.is_empty() {
16252        return explicit_owner_component_count > 1;
16253    }
16254    if lexical_scope.len() >= indexed_owner_scope.len() {
16255        return false;
16256    }
16257    let mut indexed = indexed_owner_scope.iter();
16258    lexical_scope
16259        .iter()
16260        .all(|component| indexed.any(|candidate| candidate == component))
16261}
16262
16263pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
16264    let mut current = node.parent();
16265    while let Some(parent) = current {
16266        if parent.kind() == kind {
16267            return true;
16268        }
16269        current = parent.parent();
16270    }
16271    false
16272}
16273
16274/// Whether a declaration type is initialized with a pointer cast.
16275///
16276/// This structured shape has an independent qualified occurrence in addition
16277/// to the cast descriptor below it. Other declarations must keep their normal
16278/// full-range occurrence only.
16279pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
16280    let mut current = Some(node);
16281    while let Some(candidate) = current {
16282        if candidate.kind() == "declaration" {
16283            let Some(type_node) = candidate.child_by_field_name("type") else {
16284                return false;
16285            };
16286            if !(type_node.start_byte() <= node.start_byte()
16287                && node.end_byte() <= type_node.end_byte())
16288            {
16289                return false;
16290            }
16291            let mut cursor = candidate.walk();
16292            return candidate.named_children(&mut cursor).any(|child| {
16293                child.kind() == "init_declarator"
16294                    && child
16295                        .child_by_field_name("value")
16296                        .is_some_and(|value| value.kind() == "cast_expression")
16297            });
16298        }
16299        current = candidate.parent();
16300    }
16301    false
16302}
16303
16304#[derive(Clone, Copy, PartialEq, Eq)]
16305pub(crate) enum QualifiedAliasReferenceKind {
16306    Ordinary,
16307    ConstructorWithExpressionArgument,
16308    ExhaustiveTemplate,
16309}
16310
16311/// Whether a qualified alias reference preserves the requested target.
16312///
16313/// The complete qualified spelling and its terminal identifier are both valid
16314/// occurrences when the visible alias path is structurally proven to name the
16315/// target. Template aliases use their bound arguments; ordinary aliases use
16316/// their structured primary chain.
16317pub(crate) fn qualified_alias_reference_preserves_target(
16318    node: Node<'_>,
16319    target: &CodeUnit,
16320    analyzer: &CppGraphSource<'_>,
16321    visibility: &VisibilityIndex<'_>,
16322    file: &ProjectFile,
16323    source: &str,
16324) -> Option<QualifiedAliasReferenceKind> {
16325    if !matches!(
16326        node.kind(),
16327        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16328    ) {
16329        return None;
16330    }
16331    let components = cpp_type_name_components(node, source)?;
16332    let name = components.last()?;
16333    analyzer.type_alias_provider().and_then(|provider| {
16334        visibility
16335            .visible_identifier_candidates(file, name)
16336            .find_map(|candidate| {
16337                let proof = provider.is_type_alias(candidate)
16338                    && canonical_cpp_scope_components(candidate) == components
16339                    && visibility.external_type_candidate_visible_in_context(
16340                        analyzer, file, candidate, node,
16341                    )
16342                    && match cpp_template_reference_arguments(node, source) {
16343                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
16344                            analyzer, file, candidate, &arguments, target,
16345                        ),
16346                        None => visibility.structured_alias_primary_preserves_target(
16347                            analyzer, file, candidate, target,
16348                        ),
16349                    };
16350                proof.then(|| {
16351                    if cpp_template_reference_arguments(node, source).is_some()
16352                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
16353                            analyzer, file, candidate,
16354                        )
16355                    {
16356                        QualifiedAliasReferenceKind::ExhaustiveTemplate
16357                    } else if qualified_alias_constructor_has_expression_argument(node)
16358                        || qualified_alias_local_constructor_declaration(node)
16359                    {
16360                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16361                    } else {
16362                        QualifiedAliasReferenceKind::Ordinary
16363                    }
16364                })
16365            })
16366    })
16367}
16368
16369pub(crate) fn qualified_alias_reference_requires_terminal(
16370    reference: Option<QualifiedAliasReferenceKind>,
16371) -> bool {
16372    matches!(
16373        reference,
16374        Some(
16375            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
16376                | QualifiedAliasReferenceKind::ExhaustiveTemplate
16377        )
16378    )
16379}
16380
16381fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
16382    let Some(declaration) = node.parent().filter(|parent| {
16383        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16384    }) else {
16385        return false;
16386    };
16387    let mut cursor = declaration.walk();
16388    declaration.named_children(&mut cursor).any(|child| {
16389        child.kind() == "init_declarator"
16390            && child
16391                .child_by_field_name("value")
16392                .filter(|value| value.kind() == "argument_list")
16393                .is_some_and(|arguments| {
16394                    let mut cursor = arguments.walk();
16395                    arguments.named_children(&mut cursor).any(|argument| {
16396                        let is_parameter = matches!(
16397                            argument.kind(),
16398                            "parameter_declaration" | "optional_parameter_declaration"
16399                        );
16400                        if is_parameter {
16401                            argument
16402                                .child_by_field_name("type")
16403                                .is_some_and(|type_node| {
16404                                    type_node.kind() == "type_identifier"
16405                                        && argument.child_by_field_name("declarator").is_none()
16406                                })
16407                        } else {
16408                            !argument.kind().ends_with("_literal")
16409                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
16410                        }
16411                    })
16412                })
16413    })
16414}
16415
16416/// Tree-sitter represents a local C++ direct construction such as
16417/// `Alias value(argument)` as a function declarator. Restrict that recovery to
16418/// declarations inside a compound statement so namespace-scope function
16419/// declarations with the same qualified return type stay full-range only.
16420fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
16421    let Some(declaration) = node.parent().filter(|parent| {
16422        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
16423    }) else {
16424        return false;
16425    };
16426    if declaration
16427        .parent()
16428        .is_none_or(|parent| parent.kind() != "compound_statement")
16429    {
16430        return false;
16431    }
16432    let mut cursor = declaration.walk();
16433    declaration
16434        .named_children(&mut cursor)
16435        .any(|child| child.kind() == "function_declarator")
16436}
16437
16438/// Return the terminal identifier represented by a callable or type callee.
16439///
16440/// Qualified, scoped, template, and field wrappers are traversed through their
16441/// grammar fields so both function calls and type constructions emit the token
16442/// that names the referenced declaration.
16443pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
16444    loop {
16445        let next = match node.kind() {
16446            "qualified_identifier"
16447            | "scoped_identifier"
16448            | "template_method"
16449            | "template_function"
16450            | "template_type" => node.child_by_field_name("name"),
16451            "field_expression" => node.child_by_field_name("field"),
16452            _ => None,
16453        };
16454        let Some(next) = next else {
16455            return node;
16456        };
16457        node = next;
16458    }
16459}
16460
16461#[derive(Clone, Copy)]
16462pub struct RecoveredRelationalTemplateMemberCall<'tree> {
16463    pub receiver: Node<'tree>,
16464    pub member: Node<'tree>,
16465    pub arity: usize,
16466}
16467
16468/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
16469/// nested relational expressions instead of a `template_method` call.
16470///
16471/// The recovery uses only grammar fields: the selected field must be the left
16472/// side of `<`, that expression must be the left side of `>`, and the right
16473/// side of `>` must be the parenthesized call arguments. Semantic callers must
16474/// additionally prove the receiver owner and the member's template status.
16475pub fn recovered_relational_template_member_call(
16476    field: Node<'_>,
16477) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
16478    if field.kind() != "field_expression" {
16479        return None;
16480    }
16481    let receiver = field
16482        .child_by_field_name("argument")
16483        .or_else(|| field.child_by_field_name("object"))?;
16484    let member = field.child_by_field_name("field")?;
16485    let less = field.parent()?;
16486    if less.kind() != "binary_expression"
16487        || less.child_by_field_name("left") != Some(field)
16488        || less
16489            .child_by_field_name("operator")
16490            .is_none_or(|operator| operator.kind() != "<")
16491        || less.child_by_field_name("right").is_none()
16492    {
16493        return None;
16494    }
16495    let greater = less.parent()?;
16496    if greater.kind() != "binary_expression"
16497        || greater.child_by_field_name("left") != Some(less)
16498        || greater
16499            .child_by_field_name("operator")
16500            .is_none_or(|operator| operator.kind() != ">")
16501    {
16502        return None;
16503    }
16504    let arguments = greater.child_by_field_name("right")?;
16505    if arguments.kind() != "parenthesized_expression" {
16506        return None;
16507    }
16508    let arity = parenthesized_call_argument_arity(arguments)?;
16509    Some(RecoveredRelationalTemplateMemberCall {
16510        receiver,
16511        member,
16512        arity,
16513    })
16514}
16515
16516fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
16517    let expression = arguments.named_child(0)?;
16518    if expression.kind() != "comma_expression" {
16519        return Some(1);
16520    }
16521    let mut arity = 0usize;
16522    let mut stack = vec![expression];
16523    while let Some(node) = stack.pop() {
16524        if node.kind() == "comma_expression" {
16525            stack.push(node.child_by_field_name("right")?);
16526            stack.push(node.child_by_field_name("left")?);
16527        } else {
16528            arity += 1;
16529        }
16530    }
16531    Some(arity)
16532}
16533
16534/// Whether `node` is part of a call's callee expression, walking only through
16535/// the grammar wrappers that can structurally contain that callee.
16536pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
16537    while let Some(parent) = node.parent() {
16538        match parent.kind() {
16539            "call_expression" => {
16540                return parent
16541                    .child_by_field_name("function")
16542                    .or_else(|| parent.named_child(0))
16543                    == Some(node);
16544            }
16545            "qualified_identifier"
16546            | "scoped_identifier"
16547            | "template_function"
16548            | "template_type"
16549            | "field_expression" => node = parent,
16550            _ => return false,
16551        }
16552    }
16553    false
16554}
16555
16556pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
16557    if is_call_callee_node(node) {
16558        function_terminal_node(node)
16559    } else {
16560        node
16561    }
16562}
16563
16564pub fn normalize_type_text(value: &str) -> String {
16565    strip_tag_type_prefix(
16566        normalize_cpp_whitespace(value)
16567            .trim_start_matches("const ")
16568            .trim_end_matches('*')
16569            .trim_end_matches('&')
16570            .trim(),
16571    )
16572    .to_string()
16573}
16574
16575fn strip_tag_type_prefix(value: &str) -> &str {
16576    let value = value.trim_start_matches("const ");
16577    value
16578        .strip_prefix("struct ")
16579        .or_else(|| value.strip_prefix("class "))
16580        .or_else(|| value.strip_prefix("enum "))
16581        .unwrap_or(value)
16582        .trim()
16583}
16584
16585pub fn normalize_reference_name(value: &str) -> Option<String> {
16586    let normalized = normalize_cpp_reference_text(value);
16587    (!normalized.is_empty()).then_some(normalized)
16588}
16589
16590pub fn normalize_cpp_reference_text(value: &str) -> String {
16591    let mut text = normalize_cpp_whitespace(value)
16592        .trim_start_matches("new ")
16593        .trim()
16594        .to_string();
16595    if let Some(index) = text.find(['(', '{']) {
16596        text.truncate(index);
16597    }
16598    if let Some(index) = text.find('<') {
16599        text.truncate(index);
16600    }
16601    let normalized = text
16602        .trim()
16603        .trim_start_matches("const ")
16604        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
16605        .trim_matches(':')
16606        .trim();
16607    strip_tag_type_prefix(normalized).to_string()
16608}
16609
16610pub fn cpp_name_for(unit: &CodeUnit) -> String {
16611    let short = unit.short_name().replace(['.', '$'], "::");
16612    if unit.package_name().is_empty() {
16613        short
16614    } else {
16615        format!("{}::{}", unit.package_name(), short)
16616    }
16617}
16618
16619/// Render an indexed C++ qualified name from its authoritative FqName
16620/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
16621/// that belong to a template argument (for example `Args...`).
16622fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
16623    let fq = unit.fq();
16624    if fq.is_empty() {
16625        return None;
16626    }
16627    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16628    Some(
16629        fq.segments()
16630            .iter()
16631            .map(|&segment| interner.resolve(segment).0)
16632            .collect::<Vec<_>>()
16633            .join("::"),
16634    )
16635}
16636
16637fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
16638    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
16639        || unit.fq().is_empty() && cpp_name_for(unit) == expected
16640}
16641
16642/// Return the indexed C++ owner scope without reparsing its rendered name.
16643///
16644/// Template spellings are opaque within an indexed `FqName` segment.  In
16645/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
16646/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
16647/// through `parse_symbol_path` would mistake those dots for component
16648/// separators.  Cache-loaded/legacy units may still have an empty structured
16649/// name, so retain the parser only as that explicit fallback.
16650pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
16651    let fq = unit.fq();
16652    if !fq.is_empty() {
16653        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
16654        let scope = fq
16655            .segments()
16656            .iter()
16657            .filter_map(|&segment| {
16658                let (text, kind) = interner.resolve(segment);
16659                matches!(
16660                    kind,
16661                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
16662                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
16663                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
16664                )
16665                .then(|| text.to_string())
16666            })
16667            .collect();
16668        return scope;
16669    }
16670    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16671        brokk_bifrost_core::analyzer::Language::Cpp,
16672        &cpp_name_for(unit),
16673    )
16674}
16675
16676// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
16677// (not the substring "->"), which deliberately reduces an `operator->`-style
16678// terminal segment to an empty tail rather than keeping it intact; the shared
16679// structured splitter's cpp operator-token merge would keep `operator->`
16680// whole instead, changing this function's result — `name_matches_callable`'s
16681// `expected.starts_with("operator")` fallback exists specifically to
16682// compensate for that reduction, and a pinned regression test
16683// (`operator-> must not be reduced with terminal_name-style punctuation
16684// splitting`) asserts today's char-class behavior. Not equivalence-provable;
16685// revisit alongside that pinned test if it is ever relaxed.
16686pub fn terminal_name(value: &str) -> &str {
16687    value
16688        .rsplit("::")
16689        .next()
16690        .unwrap_or(value)
16691        .rsplit(['.', '-', '>'])
16692        .next()
16693        .unwrap_or(value)
16694        .trim()
16695}
16696
16697pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
16698    terminal_name(&normalize_cpp_reference_text(value)) == expected
16699}
16700
16701pub fn name_matches_callable(value: &str, expected: &str) -> bool {
16702    name_matches_terminal(value, expected)
16703        || expected.starts_with("operator")
16704            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
16705}
16706
16707pub fn name_mentions(value: &str, expected: &str) -> bool {
16708    normalize_cpp_reference_text(value)
16709        .split("::")
16710        .any(|part| part == expected)
16711}
16712
16713pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
16714    let cpp_name = cpp_name_for(unit);
16715    if reference.contains("::") {
16716        return reference == cpp_name;
16717    }
16718    reference == cpp_name
16719        || terminal_name(reference) == unit.identifier()
16720            && (unit.package_name().is_empty() || reference == unit.identifier())
16721}
16722
16723pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
16724    match kind {
16725        TargetKind::Type
16726        | TargetKind::Constructor
16727        | TargetKind::Method
16728        | TargetKind::MemberField => true,
16729        TargetKind::FreeFunction => unit.is_function(),
16730        TargetKind::GlobalField => unit.is_field(),
16731        TargetKind::Macro => unit.is_macro(),
16732    }
16733}
16734
16735pub fn is_type_alias(unit: &CodeUnit) -> bool {
16736    unit.kind() == CodeUnitType::Field
16737        && unit.signature().is_some_and(|signature| {
16738            signature.starts_with("typedef ") || signature.starts_with("using ")
16739        })
16740}
16741
16742fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
16743    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
16744    let target_name = cpp_name_for(target);
16745    if normalized.contains("::") {
16746        return normalized == target_name;
16747    }
16748    if let Some(namespace) = alias.namespace.as_deref() {
16749        return namespace_prefixes(namespace)
16750            .into_iter()
16751            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
16752    }
16753    target.package_name().is_empty() && normalized == target.identifier()
16754}
16755
16756/// The declared return type text of a C++ function unit, with leading declaration specifiers
16757/// stripped, e.g. `T*` for `T* operator->()`.
16758pub fn cpp_function_return_type_text(
16759    analyzer: &CppGraphSource<'_>,
16760    function: &CodeUnit,
16761) -> Option<String> {
16762    let metadata = analyzer.signature_metadata(function);
16763    if !metadata.is_empty() {
16764        let first = metadata.first()?.return_type_text()?;
16765        return metadata
16766            .iter()
16767            .all(|metadata| metadata.return_type_text() == Some(first))
16768            .then(|| first.to_string());
16769    }
16770    let signature = cpp_function_signature_text(analyzer, function)?;
16771    cpp_function_return_type_text_from_signature(&signature)
16772}
16773
16774fn cpp_function_signature_text(
16775    analyzer: &CppGraphSource<'_>,
16776    function: &CodeUnit,
16777) -> Option<String> {
16778    function
16779        .signature()
16780        .filter(|signature| signature.contains(function.identifier()))
16781        .map(str::to_string)
16782        .or_else(|| analyzer.signatures(function).first().cloned())
16783        .or_else(|| analyzer.get_source(function, false))
16784}
16785
16786fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
16787    let open = signature.find('(')?;
16788    let name_at = cpp_function_name_start(signature, open)?;
16789    if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
16790        return Some(return_type);
16791    }
16792    let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
16793        .split_whitespace()
16794        .filter(|token| {
16795            !matches!(
16796                *token,
16797                "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
16798            )
16799        })
16800        .collect::<Vec<_>>()
16801        .join(" ");
16802    let type_text = type_text.trim();
16803    (!type_text.is_empty()).then(|| type_text.to_string())
16804}
16805
16806fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
16807    let before_parameters = &signature[..open];
16808    if let Some(operator_at) = before_parameters.rfind("operator") {
16809        let boundary = operator_at == 0
16810            || before_parameters[..operator_at]
16811                .chars()
16812                .next_back()
16813                .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
16814        if boundary {
16815            return Some(operator_at);
16816        }
16817    }
16818    before_parameters
16819        .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
16820        .map(|index| index + 1)
16821}
16822
16823fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
16824    let open = signature_from_name.find('(')?;
16825    let mut depth = 0i32;
16826    for (offset, ch) in signature_from_name[open..].char_indices() {
16827        match ch {
16828            '(' => depth += 1,
16829            ')' => {
16830                depth -= 1;
16831                if depth == 0 {
16832                    let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
16833                    let arrow = rest.find("->")?;
16834                    let return_type = rest[arrow + 2..].trim_start();
16835                    let return_type = return_type
16836                        .split(['{', ';'])
16837                        .next()
16838                        .unwrap_or(return_type)
16839                        .trim();
16840                    return (!return_type.is_empty()).then(|| return_type.to_string());
16841                }
16842            }
16843            _ => {}
16844        }
16845    }
16846    None
16847}
16848
16849/// Strip a leading `template <...>` parameter clause, leaving the declaration that follows.
16850/// Returns the input unchanged when there is no such clause.
16851fn cpp_strip_leading_template_clause(text: &str) -> &str {
16852    let trimmed = text.trim_start();
16853    let Some(rest) = trimmed.strip_prefix("template") else {
16854        return text;
16855    };
16856    let rest = rest.trim_start();
16857    if !rest.starts_with('<') {
16858        return text;
16859    }
16860    let mut depth = 0i32;
16861    for (offset, ch) in rest.char_indices() {
16862        match ch {
16863            '<' => depth += 1,
16864            '>' => {
16865                depth -= 1;
16866                if depth == 0 {
16867                    return rest[offset + ch.len_utf8()..].trim_start();
16868                }
16869            }
16870            _ => {}
16871        }
16872    }
16873    text
16874}
16875
16876pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
16877    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
16878    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
16879    // the same string `default_parent_fq_name`/`fq().parent()` would render:
16880    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
16881    // `::`) between a trailing `Package` segment and a following `Type`
16882    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
16883    // popping the unit's own `fq()` segment would NOT reproduce this
16884    // fully-`::`-joined string. Left as a split on the locally-built
16885    // all-colon string rather than the unit's structured name.
16886    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
16887        namespace
16888            .strip_prefix("anonymous_namespace::")
16889            .unwrap_or(namespace)
16890            .to_string()
16891    })
16892}
16893
16894fn namespace_prefixes(namespace: &str) -> Vec<String> {
16895    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
16896    // non-`::` separator already converted to `::`, so re-tokenizing it with
16897    // the shared structured splitter and progressively popping the last
16898    // component reproduces the `rsplit_once("::")` outward walk exactly (same
16899    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
16900    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16901        brokk_bifrost_core::analyzer::Language::Cpp,
16902        namespace,
16903    );
16904    let mut prefixes = Vec::new();
16905    while !parts.is_empty() {
16906        prefixes.push(parts.join("::"));
16907        parts.pop();
16908    }
16909    prefixes
16910}
16911
16912fn nearest_namespace_candidates(
16913    candidates: Vec<CodeUnit>,
16914    normalized: &str,
16915    lexical_namespace: Option<&str>,
16916) -> Vec<CodeUnit> {
16917    if normalized.contains("::") {
16918        return candidates;
16919    }
16920    if let Some(namespace) = lexical_namespace {
16921        for prefix in namespace_prefixes(namespace) {
16922            let scoped = candidates
16923                .iter()
16924                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
16925                .cloned()
16926                .collect::<Vec<_>>();
16927            if !scoped.is_empty() {
16928                return scoped;
16929            }
16930        }
16931    }
16932    candidates
16933        .into_iter()
16934        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
16935        .collect()
16936}
16937
16938pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
16939    let mut namespaces = Vec::new();
16940    let mut current = node.parent();
16941    while let Some(parent) = current {
16942        if parent.kind() == "namespace_definition"
16943            && let Some(name) = parent.child_by_field_name("name")
16944        {
16945            let namespace = normalize_cpp_reference_text(node_text(name, source));
16946            if !namespace.is_empty() {
16947                namespaces.push(namespace);
16948            }
16949        }
16950        current = parent.parent();
16951    }
16952    if namespaces.is_empty() {
16953        None
16954    } else {
16955        namespaces.reverse();
16956        Some(namespaces.join("::"))
16957    }
16958}
16959
16960/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
16961/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
16962/// globals rather than members.
16963pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
16964    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
16965}
16966
16967fn type_owner_resolution(
16968    analyzer: &CppGraphSource<'_>,
16969    code_unit: &CodeUnit,
16970) -> Option<ResolvedTypeOwner> {
16971    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
16972}
16973
16974fn target_type_owner_resolution(
16975    analyzer: &CppGraphSource<'_>,
16976    code_unit: &CodeUnit,
16977) -> Option<ResolvedTypeOwner> {
16978    match type_owner_resolution(analyzer, code_unit) {
16979        Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
16980        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
16981    }
16982}
16983
16984/// Recover method identity for an indexed out-of-line definition when the
16985/// ordinary parent edge is absent. Prefer the unique include-visible forward
16986/// declaration, then classify exact-FQN class declarations elsewhere in the
16987/// workspace. A unique complete declaration wins; otherwise multiple forward
16988/// declarations are one owner only when they all share one logical identity.
16989/// The qualified callable FQN proves that owner spelling even when its defining
16990/// header is outside the scan file's include closure, while unknown or competing
16991/// complete declarations remain ambiguous.
16992/// This is deliberately target-only: canonical declaration resolution must
16993/// continue to prefer the callable definition rather than replacing it with
16994/// the recovered owner.
16995fn target_forward_owner_resolution(
16996    analyzer: &CppGraphSource<'_>,
16997    code_unit: &CodeUnit,
16998) -> Option<ResolvedTypeOwner> {
16999    if !code_unit.is_function() {
17000        return None;
17001    }
17002    // A top-level free function has no owner at all, and `FqName::parent`
17003    // answers the empty name rather than `None` for a one-segment identity.
17004    // `default_parent_fq_name`, which this replaced, filtered that case out;
17005    // asking the relational store for the empty name is a batch error that
17006    // fails the whole target frontier.
17007    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
17008    let cpp = analyzer.cpp?;
17009    let mut visible_files = HashSet::default();
17010    collect_include_closure(
17011        analyzer,
17012        cpp.include_target_index(),
17013        code_unit.source(),
17014        &mut visible_files,
17015        None,
17016    );
17017    let candidates = analyzer.workspace_definitions().exact(&owner_name);
17018    let visible_candidates = candidates
17019        .iter()
17020        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17021        .cloned()
17022        .collect::<Vec<_>>();
17023    match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17024        DirectOwnerResolution::UniqueFull(unit) => {
17025            return Some(ResolvedTypeOwner {
17026                unit,
17027                is_forward_declaration: false,
17028            });
17029        }
17030        DirectOwnerResolution::ForwardsOnly(forwards) => {
17031            return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17032                unit: forwards.into_iter().next().unwrap(),
17033                is_forward_declaration: true,
17034            });
17035        }
17036        DirectOwnerResolution::Ambiguous => return None,
17037        DirectOwnerResolution::None => {}
17038    }
17039
17040    let candidates = candidates
17041        .into_iter()
17042        .filter(|candidate| candidate.is_class())
17043        .collect::<Vec<_>>();
17044    let (unit, is_forward_declaration) =
17045        match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17046            DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17047            DirectOwnerResolution::ForwardsOnly(forwards) => {
17048                (unique_logical_forward_owner(forwards)?, true)
17049            }
17050            DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17051        };
17052    Some(ResolvedTypeOwner {
17053        unit,
17054        is_forward_declaration,
17055    })
17056}
17057
17058pub fn precise_parent_of(
17059    analyzer: &CppGraphSource<'_>,
17060    visibility: &VisibilityIndex<'_>,
17061    code_unit: &CodeUnit,
17062) -> Option<CodeUnit> {
17063    visibility.cached_precise_parent_of(analyzer, code_unit)
17064}
17065
17066fn precise_parent_resolution(
17067    analyzer: &CppGraphSource<'_>,
17068    code_unit: &CodeUnit,
17069) -> Option<ResolvedTypeOwner> {
17070    #[cfg(any(test, feature = "test-support"))]
17071    if let Some(cpp) = analyzer.cpp {
17072        cpp.record_cpp_parent_resolution_for_test();
17073    }
17074    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17075        return Some(ResolvedTypeOwner {
17076            unit,
17077            is_forward_declaration: false,
17078        });
17079    }
17080    let fallback = analyzer.parent_of(code_unit);
17081    if !code_unit.owner_is_type_scope() {
17082        return fallback.map(|unit| ResolvedTypeOwner {
17083            unit,
17084            is_forward_declaration: false,
17085        });
17086    }
17087    let owner_fq = code_unit
17088        .fq()
17089        .parent()
17090        .expect("a unit with an owner identifier has a structured parent");
17091    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17092    match same_source_owner(analyzer, code_unit, &owner_candidates) {
17093        DirectOwnerResolution::UniqueFull(owner) => {
17094            return Some(ResolvedTypeOwner {
17095                unit: owner,
17096                is_forward_declaration: false,
17097            });
17098        }
17099        DirectOwnerResolution::Ambiguous => return None,
17100        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17101    }
17102    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17103        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17104            unit: owner,
17105            is_forward_declaration: false,
17106        }),
17107        DirectOwnerResolution::Ambiguous => None,
17108        DirectOwnerResolution::ForwardsOnly(forwards) => {
17109            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17110                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17111                    unit: owner,
17112                    is_forward_declaration: false,
17113                }),
17114                FullOwnerResolution::None => {
17115                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17116                        unit,
17117                        is_forward_declaration: true,
17118                    })
17119                }
17120                FullOwnerResolution::Ambiguous => None,
17121            }
17122        }
17123        DirectOwnerResolution::None => {
17124            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17125                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17126                    unit: owner,
17127                    is_forward_declaration: false,
17128                }),
17129                FullOwnerResolution::Ambiguous => None,
17130                FullOwnerResolution::None => fallback
17131                    .filter(|parent| {
17132                        parent.source() == code_unit.source()
17133                            && parent.fq() == &owner_fq
17134                            && (!parent.is_class()
17135                                || cpp_class_declaration_strength(analyzer, parent)
17136                                    == CppClassDeclarationStrength::Full)
17137                    })
17138                    .map(|unit| ResolvedTypeOwner {
17139                        unit,
17140                        is_forward_declaration: false,
17141                    }),
17142            }
17143        }
17144    }
17145}
17146
17147fn exact_structural_type_parent(
17148    analyzer: &CppGraphSource<'_>,
17149    code_unit: &CodeUnit,
17150) -> Option<CodeUnit> {
17151    if !code_unit.is_function() && !code_unit.is_field() {
17152        return None;
17153    }
17154    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
17155    let cpp = analyzer.cpp?;
17156    let parent = cpp.structural_parent_of(code_unit)?;
17157    (!parent.is_module()
17158        && parent.source() == code_unit.source()
17159        && parent.package_name() == code_unit.package_name()
17160        && parent.short_name() == encoded_owner)
17161        .then_some(parent)
17162}
17163
17164fn same_source_owner(
17165    analyzer: &CppGraphSource<'_>,
17166    code_unit: &CodeUnit,
17167    owner_candidates: &[CodeUnit],
17168) -> DirectOwnerResolution {
17169    let candidates = owner_candidates
17170        .iter()
17171        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
17172        .cloned()
17173        .collect::<Vec<_>>();
17174    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17175    classify_direct_owner_candidates(analyzer, candidates.into_iter())
17176}
17177
17178fn visible_full_cpp_owner(
17179    analyzer: &CppGraphSource<'_>,
17180    code_unit: &CodeUnit,
17181    owner_candidates: &[CodeUnit],
17182) -> FullOwnerResolution {
17183    let Some(cpp) = analyzer.cpp else {
17184        return FullOwnerResolution::None;
17185    };
17186    let mut visible_files = HashSet::default();
17187    collect_include_closure(
17188        analyzer,
17189        cpp.include_target_index(),
17190        code_unit.source(),
17191        &mut visible_files,
17192        None,
17193    );
17194    let candidates = owner_candidates
17195        .iter()
17196        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17197        .cloned()
17198        .collect::<Vec<_>>();
17199    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17200    let mut full_definition = None;
17201    for candidate in candidates {
17202        match cpp_class_declaration_strength(analyzer, &candidate) {
17203            CppClassDeclarationStrength::Full if full_definition.is_some() => {
17204                return FullOwnerResolution::Ambiguous;
17205            }
17206            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17207            CppClassDeclarationStrength::Forward => {}
17208            CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
17209        }
17210    }
17211    full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
17212}
17213
17214pub enum DirectOwnerResolution {
17215    None,
17216    ForwardsOnly(Vec<CodeUnit>),
17217    UniqueFull(CodeUnit),
17218    Ambiguous,
17219}
17220
17221enum FullOwnerResolution {
17222    None,
17223    Unique(CodeUnit),
17224    Ambiguous,
17225}
17226
17227#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17228pub enum CppClassDeclarationStrength {
17229    Full,
17230    Forward,
17231    Unknown,
17232}
17233
17234fn directly_included_owner(
17235    analyzer: &CppGraphSource<'_>,
17236    code_unit: &CodeUnit,
17237    owner_candidates: &[CodeUnit],
17238) -> DirectOwnerResolution {
17239    let Some(cpp) = analyzer.cpp else {
17240        return DirectOwnerResolution::None;
17241    };
17242    let imports = analyzer.import_statements(code_unit.source());
17243    let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
17244        .into_iter()
17245        .flat_map(|include| {
17246            resolve_include_targets_with_index(
17247                code_unit.source(),
17248                &include,
17249                cpp.include_target_index(),
17250            )
17251        })
17252        .collect();
17253    let candidates = owner_candidates
17254        .iter()
17255        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
17256        .cloned()
17257        .collect::<Vec<_>>();
17258    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17259    classify_direct_owner_candidates(analyzer, candidates.into_iter())
17260}
17261
17262fn prefer_member_declaring_owners(
17263    analyzer: &CppGraphSource<'_>,
17264    member: &CodeUnit,
17265    candidates: Vec<CodeUnit>,
17266) -> Vec<CodeUnit> {
17267    let matching = candidates
17268        .iter()
17269        .filter(|owner| owner_declares_member(analyzer, owner, member))
17270        .cloned()
17271        .collect::<Vec<_>>();
17272    if matching.is_empty() {
17273        candidates
17274    } else {
17275        matching
17276    }
17277}
17278
17279fn owner_declares_member(
17280    analyzer: &CppGraphSource<'_>,
17281    owner: &CodeUnit,
17282    member: &CodeUnit,
17283) -> bool {
17284    analyzer.direct_children(owner).into_iter().any(|child| {
17285        child.kind() == member.kind()
17286            && child.identifier() == member.identifier()
17287            && child.signature() == member.signature()
17288    })
17289}
17290
17291fn classify_direct_owner_candidates(
17292    analyzer: &CppGraphSource<'_>,
17293    candidates: impl Iterator<Item = CodeUnit>,
17294) -> DirectOwnerResolution {
17295    collapse_owner_candidates(candidates.map(|candidate| {
17296        let strength = cpp_class_declaration_strength(analyzer, &candidate);
17297        (candidate, strength)
17298    }))
17299}
17300
17301pub fn collapse_owner_candidates(
17302    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
17303) -> DirectOwnerResolution {
17304    let mut full_definition = None;
17305    let mut forwards = Vec::new();
17306    for (candidate, strength) in candidates {
17307        match strength {
17308            CppClassDeclarationStrength::Full if full_definition.is_some() => {
17309                return DirectOwnerResolution::Ambiguous;
17310            }
17311            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
17312            CppClassDeclarationStrength::Forward => forwards.push(candidate),
17313            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
17314        }
17315    }
17316    if let Some(owner) = full_definition {
17317        DirectOwnerResolution::UniqueFull(owner)
17318    } else if !forwards.is_empty() {
17319        DirectOwnerResolution::ForwardsOnly(forwards)
17320    } else {
17321        DirectOwnerResolution::None
17322    }
17323}
17324
17325#[cfg(any(test, feature = "test-support"))]
17326pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17327    unique_logical_forward_owner(forwards)
17328}
17329
17330fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
17331    let first = forwards.pop()?;
17332    forwards
17333        .iter()
17334        .all(|forward| same_logical_symbol(forward, &first))
17335        .then_some(first)
17336}
17337
17338pub fn cpp_class_declaration_strength(
17339    analyzer: &CppGraphSource<'_>,
17340    candidate: &CodeUnit,
17341) -> CppClassDeclarationStrength {
17342    // The answer is a pure function of the unit's ranges and its file's tree,
17343    // and the inverse scan asks it once per declaration seed. On a translation
17344    // unit the parser could not fully recover, each ask re-derives the
17345    // export-macro recovery shapes from the file's `ERROR` subtrees, so without
17346    // this memo one file's scan is quadratic in its own size: 97% of Catch2's
17347    // 284 s inverse scan of `extras/catch_amalgamated.cpp` was in this call
17348    // (#1496).
17349    let Some(cpp) = analyzer.cpp else {
17350        return uncached_cpp_class_declaration_strength(analyzer, candidate);
17351    };
17352    if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
17353        return strength;
17354    }
17355    let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
17356    cpp.cache_class_declaration_strength(candidate, strength);
17357    strength
17358}
17359
17360fn uncached_cpp_class_declaration_strength(
17361    analyzer: &CppGraphSource<'_>,
17362    candidate: &CodeUnit,
17363) -> CppClassDeclarationStrength {
17364    if let Some(cpp) = analyzer.cpp
17365        && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
17366    {
17367        return cpp_class_declaration_strength_in_tree(
17368            analyzer,
17369            &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
17370            candidate,
17371            prepared.source(),
17372            prepared.tree().root_node(),
17373        );
17374    }
17375    let Some(source) = analyzer.indexed_source(candidate.source()) else {
17376        return CppClassDeclarationStrength::Unknown;
17377    };
17378    #[cfg(any(test, feature = "test-support"))]
17379    if let Some(cpp) = analyzer.cpp {
17380        cpp.record_cpp_class_strength_parse_for_test();
17381    }
17382    let mut parser = Parser::new();
17383    if parser
17384        .set_language(&tree_sitter_cpp::LANGUAGE.into())
17385        .is_err()
17386    {
17387        return CppClassDeclarationStrength::Unknown;
17388    }
17389    let Some(tree) = parser.parse(&source, None) else {
17390        return CppClassDeclarationStrength::Unknown;
17391    };
17392    // This branch reparses a file the analyzer has no prepared tree for, so its
17393    // recovery index is that one tree's and cannot be shared.
17394    let recovered_export_classes =
17395        CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
17396    cpp_class_declaration_strength_in_tree(
17397        analyzer,
17398        &recovered_export_classes,
17399        candidate,
17400        &source,
17401        tree.root_node(),
17402    )
17403}
17404
17405fn cpp_class_declaration_strength_in_tree(
17406    analyzer: &CppGraphSource<'_>,
17407    recovered_export_classes: &CppRecoveredExportClassIndex,
17408    candidate: &CodeUnit,
17409    source: &str,
17410    root: Node<'_>,
17411) -> CppClassDeclarationStrength {
17412    let ranges = analyzer.ranges(candidate);
17413    let mut saw_forward = false;
17414    for range in ranges {
17415        // The recovered export-macro shapes answer for their own ranges; only a
17416        // range no recovery claims is read as a plain specifier.
17417        match recovered_class_body_at(
17418            recovered_export_classes,
17419            root,
17420            source,
17421            candidate.identifier(),
17422            &range,
17423        ) {
17424            Some(true) => return CppClassDeclarationStrength::Full,
17425            Some(false) => {
17426                saw_forward = true;
17427                continue;
17428            }
17429            None => {}
17430        }
17431        // Only a node covering the range's start byte can be the specifier for
17432        // this range, so apply that test where nodes enter the stack rather
17433        // than where they leave it. Pushing first meant one ask enqueued every
17434        // sibling at every level it descended, which on a translation unit with
17435        // thousands of top-level declarations is a per-ask cost proportional to
17436        // the file (#1496).
17437        let covers_range_start = |node: &Node<'_>| {
17438            node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
17439        };
17440        let mut stack = Vec::new();
17441        if covers_range_start(&root) {
17442            stack.push(root);
17443        }
17444        while let Some(node) = stack.pop() {
17445            if node.start_byte() == range.start_byte
17446                && node.end_byte() == range.end_byte
17447                && matches!(
17448                    node.kind(),
17449                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
17450                )
17451            {
17452                if cpp_class_node_has_body(node) {
17453                    return CppClassDeclarationStrength::Full;
17454                }
17455                saw_forward = true;
17456            }
17457            let mut cursor = node.walk();
17458            stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
17459        }
17460    }
17461    if saw_forward {
17462        CppClassDeclarationStrength::Forward
17463    } else {
17464        CppClassDeclarationStrength::Unknown
17465    }
17466}
17467
17468fn cpp_class_node_has_body(node: Node<'_>) -> bool {
17469    node.child_by_field_name("body").is_some() || {
17470        let mut cursor = node.walk();
17471        node.named_children(&mut cursor).any(|child| {
17472            matches!(
17473                child.kind(),
17474                "declaration_list" | "field_declaration_list" | "enumerator_list"
17475            )
17476        })
17477    }
17478}
17479
17480#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17481enum CppCTagKind {
17482    Struct,
17483    Union,
17484}
17485
17486fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
17487    let declaration = analyzer.get_source(code_unit, false)?;
17488    let mut parser = Parser::new();
17489    parser
17490        .set_language(&tree_sitter_cpp::LANGUAGE.into())
17491        .ok()?;
17492    let tree = parser.parse(&declaration, None)?;
17493    let mut stack = vec![tree.root_node()];
17494    while let Some(node) = stack.pop() {
17495        let kind = match node.kind() {
17496            "struct_specifier" => CppCTagKind::Struct,
17497            "union_specifier" => CppCTagKind::Union,
17498            _ => {
17499                let mut cursor = node.walk();
17500                stack.extend(node.named_children(&mut cursor));
17501                continue;
17502            }
17503        };
17504        if node
17505            .child_by_field_name("name")
17506            .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
17507        {
17508            return Some(kind);
17509        }
17510        let mut cursor = node.walk();
17511        stack.extend(node.named_children(&mut cursor));
17512    }
17513    None
17514}
17515
17516pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17517    if !code_unit.owner_is_type_scope() {
17518        return None;
17519    }
17520    let owner_fq = code_unit.fq().parent()?;
17521    ctx.analyzer
17522        .workspace_definitions()
17523        .exact(&owner_fq)
17524        .into_iter()
17525        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
17526}
17527
17528pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17529    left.kind() == right.kind()
17530        && left.fq_name() == right.fq_name()
17531        && left.signature() == right.signature()
17532        && left.source() == right.source()
17533}
17534
17535pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17536    same_symbol(left, right) || same_logical_symbol(left, right)
17537}
17538
17539pub fn same_visible_global_field_symbol(
17540    analyzer: &CppGraphSource<'_>,
17541    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
17542    left: &CodeUnit,
17543    right: &CodeUnit,
17544) -> bool {
17545    if same_symbol(left, right) {
17546        return true;
17547    }
17548    if !same_logical_symbol(left, right) {
17549        return false;
17550    }
17551    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
17552        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
17553    {
17554        left.source() == right.source()
17555    } else {
17556        true
17557    }
17558}
17559
17560fn cpp_global_field_has_internal_linkage_cached(
17561    analyzer: &CppGraphSource<'_>,
17562    cache: &mut HashMap<CodeUnit, bool>,
17563    candidate: &CodeUnit,
17564) -> bool {
17565    if let Some(internal) = cache.get(candidate) {
17566        return *internal;
17567    }
17568    #[cfg(any(test, feature = "test-support"))]
17569    note_cpp_global_field_internal_linkage_classification_for_test();
17570    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
17571    cache.insert(candidate.clone(), internal);
17572    internal
17573}
17574
17575#[cfg(any(test, feature = "test-support"))]
17576thread_local! {
17577    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17578}
17579
17580#[cfg(any(test, feature = "test-support"))]
17581fn note_cpp_global_field_internal_linkage_classification_for_test() {
17582    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17583        count.set(count.get() + 1);
17584    });
17585}
17586
17587#[cfg(any(test, feature = "test-support"))]
17588pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
17589    body: impl FnOnce() -> T,
17590) -> (T, usize) {
17591    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
17592        count.set(0);
17593        let result = body();
17594        let observed = count.get();
17595        count.set(0);
17596        (result, observed)
17597    })
17598}
17599
17600pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
17601    left.kind() == right.kind()
17602        && left.fq_name() == right.fq_name()
17603        && left.signature() == right.signature()
17604}
17605
17606pub fn cpp_global_field_has_internal_linkage(
17607    analyzer: &CppGraphSource<'_>,
17608    candidate: &CodeUnit,
17609) -> bool {
17610    if !candidate.is_field() || candidate.short_name().contains('.') {
17611        return false;
17612    }
17613    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
17614        return false;
17615    };
17616    match local_linkage {
17617        CppFieldLinkage::Internal => true,
17618        CppFieldLinkage::External => false,
17619        CppFieldLinkage::InternalUnlessExternalPeer => {
17620            !cpp_global_field_linkage_peers(analyzer, candidate)
17621                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
17622                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
17623        }
17624    }
17625}
17626
17627fn cpp_global_field_linkage_peers<'a>(
17628    analyzer: &CppGraphSource<'a>,
17629    candidate: &'a CodeUnit,
17630) -> impl Iterator<Item = CodeUnit> + 'a {
17631    let name = candidate.fq().clone();
17632    analyzer
17633        .workspace_definitions()
17634        .exact(&name)
17635        .into_iter()
17636        .filter(move |peer| {
17637            if peer == candidate {
17638                return false;
17639            }
17640            #[cfg(any(test, feature = "test-support"))]
17641            note_cpp_global_field_linkage_peer_inspection_for_test();
17642            same_logical_symbol(peer, candidate)
17643        })
17644}
17645
17646#[cfg(any(test, feature = "test-support"))]
17647thread_local! {
17648    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
17649}
17650
17651#[cfg(any(test, feature = "test-support"))]
17652fn note_cpp_global_field_linkage_peer_inspection_for_test() {
17653    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17654        count.set(count.get() + 1);
17655    });
17656}
17657
17658#[cfg(any(test, feature = "test-support"))]
17659pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
17660    body: impl FnOnce() -> T,
17661) -> (T, usize) {
17662    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
17663        count.set(0);
17664        let result = body();
17665        let observed = count.get();
17666        count.set(0);
17667        (result, observed)
17668    })
17669}
17670
17671fn cpp_global_field_declaration_linkage(
17672    analyzer: &CppGraphSource<'_>,
17673    candidate: &CodeUnit,
17674) -> Option<CppFieldLinkage> {
17675    if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
17676        return Some(linkage);
17677    }
17678    let cpp = analyzer.cpp?;
17679    if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
17680        return cpp_global_field_declaration_linkage_in_tree(
17681            analyzer,
17682            candidate,
17683            prepared.source(),
17684            prepared.tree().root_node(),
17685        );
17686    }
17687    let source = analyzer.indexed_source(candidate.source())?;
17688    let mut parser = Parser::new();
17689    if parser
17690        .set_language(&tree_sitter_cpp::LANGUAGE.into())
17691        .is_err()
17692    {
17693        return None;
17694    }
17695    let tree = parser.parse(&source, None)?;
17696    cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
17697}
17698
17699fn cpp_global_field_declaration_linkage_in_tree(
17700    analyzer: &CppGraphSource<'_>,
17701    candidate: &CodeUnit,
17702    source: &str,
17703    root: Node<'_>,
17704) -> Option<CppFieldLinkage> {
17705    analyzer.ranges(candidate).iter().find_map(|range| {
17706        node_for_exact_range(root, range)
17707            .and_then(enclosing_cpp_field_declaration)
17708            .map(|declaration| {
17709                // One question about one declaration; see `ParentIndex::unindexed`.
17710                cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
17711            })
17712    })
17713}
17714
17715fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
17716    loop {
17717        if matches!(node.kind(), "declaration" | "field_declaration") {
17718            return Some(node);
17719        }
17720        node = node.parent()?;
17721    }
17722}
17723
17724#[cfg(test)]
17725mod tests {
17726    #[test]
17727    fn issue_3089_statement_formal_at_end_of_replacement() {
17728        let parameters = vec!["handle".to_owned(), "block".to_owned()];
17729        for replacement in [
17730            "do { header_event_t* event; if ((handle)->active) block } while (0)",
17731            "do { header_event_t* event; block } while (0)",
17732        ] {
17733            assert!(
17734                super::VisibilityIndex::parse_macro_replacement_body(replacement, &parameters)
17735                    .is_some(),
17736                "{replacement}"
17737            );
17738        }
17739    }
17740    use super::*;
17741
17742    #[test]
17743    fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
17744        let source = "int size(void) { return sizeof(((Payload))); }\n";
17745        let mut parser = Parser::new();
17746        parser
17747            .set_language(&tree_sitter_cpp::LANGUAGE.into())
17748            .expect("C++ grammar");
17749        let tree = parser.parse(source, None).expect("fixture tree");
17750        let start = source.find("Payload").expect("sizeof operand");
17751        let node = tree
17752            .root_node()
17753            .named_descendant_for_byte_range(start, start + "Payload".len())
17754            .expect("focused operand");
17755        let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
17756        let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
17757
17758        assert_eq!(node.kind(), "identifier");
17759        assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
17760        assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
17761    }
17762
17763    fn parse_cpp(source: &str) -> Tree {
17764        let mut parser = Parser::new();
17765        parser
17766            .set_language(&tree_sitter_cpp::LANGUAGE.into())
17767            .expect("C++ grammar");
17768        parser.parse(source, None).expect("fixture tree")
17769    }
17770
17771    fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
17772        let start = source.find(needle).expect("fixture needle");
17773        tree.root_node()
17774            .named_descendant_for_byte_range(start, start + needle.len())
17775            .expect("node at needle")
17776    }
17777
17778    fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
17779        let mut parser = Parser::new();
17780        parser
17781            .set_language(&tree_sitter_cpp::LANGUAGE.into())
17782            .expect("C++ grammar");
17783        let tree = parser.parse(source, None).expect("fixture tree");
17784        PreparedSyntaxTree::new(
17785            PreparedSyntaxSource::Exact(Arc::from(source)),
17786            tree,
17787            compute_line_starts(source),
17788            LanguageDialect::Standard(Language::Cpp),
17789            PreparedSourceOrigin::Disk,
17790            None,
17791        )
17792    }
17793
17794    fn unresolved_include_before(source: &str, reference: &str) -> bool {
17795        let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
17796        let prepared = prepared_cpp(source);
17797        let facts = collect_structured_include_facts(&prepared);
17798        let include_targets = IncludeTargetIndex::build([&file]);
17799        has_unresolved_include_visible_before_in_prepared(
17800            &file,
17801            &prepared,
17802            &include_targets,
17803            &facts,
17804            source.find(reference).expect("reference fixture"),
17805        )
17806    }
17807
17808    #[test]
17809    fn unresolved_include_before_reference_is_visible() {
17810        let source = "#include \"missing.h\"\nint use = Missing;\n";
17811        assert!(unresolved_include_before(source, "Missing"));
17812    }
17813
17814    #[test]
17815    fn unresolved_include_after_reference_is_not_visible() {
17816        let source = "int use = Missing;\n#include \"missing.h\"\n";
17817        assert!(!unresolved_include_before(source, "Missing"));
17818    }
17819
17820    #[test]
17821    fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
17822        let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
17823        assert!(!unresolved_include_before(source, "Missing"));
17824    }
17825
17826    #[test]
17827    fn unresolved_include_in_current_branch_is_visible() {
17828        let source =
17829            "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
17830        assert!(unresolved_include_before(source, "Missing"));
17831    }
17832
17833    /// Two macro-decorated class heads make tree-sitter close `detail` at the
17834    /// first class's `}`, `matchers` at the second's, and `app` at `detail`'s
17835    /// real `}`; the tail parses at translation-unit level and the two real
17836    /// closes for `matchers` and `app` land in a trailing ERROR (#1537).
17837    const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
17838namespace matchers {
17839    namespace detail {
17840        class API [[nodiscard]] First {
17841        public:
17842            int value() const { return count_ + 1; }
17843        private:
17844            int count_;
17845        };
17846        class API [[nodiscard]] Second {
17847        public:
17848            int value() const { return count_ + 2; }
17849        private:
17850            int count_;
17851        };
17852    } // namespace detail
17853
17854    template <typename T>
17855    void tail_function(MatcherBase<T> const& value);
17856
17857    class TailClass {};
17858} // namespace matchers
17859} // namespace app
17860
17861struct AfterAll {};
17862"#;
17863
17864    #[test]
17865    fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
17866        let source = STOLEN_BRACE_CASCADE;
17867        let tree = parse_cpp(source);
17868        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
17869
17870        let tail_class = named_node_at(&tree, source, "TailClass");
17871        assert!(
17872            !has_ancestor_kind(tail_class, "namespace_definition"),
17873            "the fixture must reproduce the recovery: the tail has no namespace ancestor"
17874        );
17875        let displaced = named_node_at(&tree, source, "Second");
17876        assert_eq!(
17877            enclosing_namespace_components(displaced, source),
17878            Some(vec!["app".to_string(), "matchers".to_string()]),
17879            "the fixture must displace the second class out of detail"
17880        );
17881
17882        let components = |needle: &str| {
17883            index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
17884        };
17885        assert_eq!(components("First"), ["app", "matchers", "detail"]);
17886        assert_eq!(components("Second"), ["app", "matchers", "detail"]);
17887        assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
17888        assert_eq!(components("tail_function"), ["app", "matchers"]);
17889        assert_eq!(components("TailClass"), ["app", "matchers"]);
17890        assert!(components("AfterAll").is_empty());
17891    }
17892
17893    #[test]
17894    fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
17895        let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
17896        let tree = parse_cpp(clean);
17897        assert!(!tree.root_node().has_error());
17898        assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
17899
17900        // A namespace that merely contains a parse error closes where its
17901        // brace says; the declarations after it keep their parsed scope.
17902        let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
17903        let tree = parse_cpp(damaged);
17904        assert!(tree.root_node().has_error());
17905        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
17906        assert_eq!(
17907            index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
17908            ["a"]
17909        );
17910    }
17911
17912    /// A function-like `#define` in a class body, whose replacement returns a
17913    /// SFINAE type, makes tree-sitter give up on the whole enclosing namespace:
17914    /// `namespace app {` becomes an `ERROR` whose own `namespace`, name and `{`
17915    /// tokens are the only trace of the head, and every declaration the
17916    /// namespace holds becomes their flat sibling. This is Catch2's
17917    /// `catch_decomposer.hpp`, reduced (issue #3084).
17918    const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
17919
17920    class Target {
17921        int value_;
17922    };
17923
17924    template <typename T>
17925    class Holder {
17926    public:
17927        explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
17928
17929#define HOLDER_DEFINE_OP( id, op )                                             \
17930    template <typename U>                                                      \
17931    constexpr friend auto operator op( Holder&& lhs, U&& rhs )                 \
17932        -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> {       \
17933        return Target{};                                                       \
17934    }
17935
17936        HOLDER_DEFINE_OP( equal, == )
17937#undef HOLDER_DEFINE_OP
17938        T m_lhs;
17939    };
17940
17941    class Tail {};
17942}
17943"#;
17944
17945    #[test]
17946    fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
17947        let source = COLLAPSED_NAMESPACE_HEAD;
17948        let tree = parse_cpp(source);
17949        let target = named_node_at(&tree, source, "class Target");
17950
17951        assert!(
17952            !has_ancestor_kind(target, "namespace_definition"),
17953            "the fixture must reproduce the collapse: the class has no namespace ancestor"
17954        );
17955        let head = target.parent().expect("the collapsed namespace envelope");
17956        assert_eq!(
17957            head.kind(),
17958            "ERROR",
17959            "the fixture must keep the namespace head in an ERROR node"
17960        );
17961
17962        let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
17963        assert_eq!(
17964            index.enclosing_namespace_components(target, source),
17965            ["app"]
17966        );
17967    }
17968
17969    #[test]
17970    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
17971        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
17972        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
17973        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
17974        assert!(indexed_namespace_path_is_recoverable(
17975            &["cache".to_string()],
17976            &indexed,
17977            1,
17978        ));
17979    }
17980
17981    #[test]
17982    fn sort_lookup_units_totally_orders_every_identity_field() {
17983        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
17984        let base = CodeUnit::with_signature(
17985            file.clone(),
17986            CodeUnitType::Function,
17987            "scope",
17988            "value",
17989            Some("()".to_string()),
17990            false,
17991        );
17992        let different_kind = CodeUnit::with_signature(
17993            file.clone(),
17994            CodeUnitType::Field,
17995            "scope",
17996            "value",
17997            Some("()".to_string()),
17998            false,
17999        );
18000        let synthetic = base.with_synthetic(true);
18001
18002        let interner = segment_interner();
18003        let mut member_fq = FqName::new();
18004        member_fq.push(interner.intern("scope", SegmentKind::Package));
18005        member_fq.push(interner.intern("value", SegmentKind::Member));
18006        let different_package_boundary = CodeUnit::from_fq(
18007            file.clone(),
18008            CodeUnitType::Function,
18009            member_fq,
18010            0,
18011            Some("()".to_string()),
18012            false,
18013        );
18014
18015        let mut unknown_fq = FqName::new();
18016        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
18017        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
18018        let different_segment_kind = CodeUnit::from_fq(
18019            file,
18020            CodeUnitType::Function,
18021            unknown_fq,
18022            1,
18023            Some("()".to_string()),
18024            false,
18025        );
18026
18027        let input = vec![
18028            base,
18029            different_kind,
18030            synthetic,
18031            different_package_boundary,
18032            different_segment_kind,
18033        ];
18034        let mut expected = input.clone();
18035        sort_lookup_units(&mut expected);
18036        assert!(expected.windows(2).all(|pair| {
18037            let mut ordered = pair.to_vec();
18038            sort_lookup_units(&mut ordered);
18039            ordered == pair && pair[0] != pair[1]
18040        }));
18041
18042        let mut reversed = input.clone();
18043        reversed.reverse();
18044        sort_lookup_units(&mut reversed);
18045        assert_eq!(reversed, expected);
18046
18047        let mut rotated = input;
18048        rotated.rotate_left(2);
18049        sort_lookup_units(&mut rotated);
18050        assert_eq!(rotated, expected);
18051    }
18052
18053    #[test]
18054    fn displaced_preprocessor_terminator_bounds_the_real_guard() {
18055        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";
18056        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
18057        let parse = |source: &str| {
18058            let mut parser = Parser::new();
18059            parser
18060                .set_language(&tree_sitter_cpp::LANGUAGE.into())
18061                .expect("C++ grammar");
18062            parser.parse(source, None).expect("fixture tree")
18063        };
18064
18065        let tree = parse(damaged);
18066        let root = tree.root_node();
18067        let target = damaged.find("target").expect("target byte");
18068        let declaration = root
18069            .descendant_for_byte_range(target, target + "target".len())
18070            .and_then(|mut node| {
18071                loop {
18072                    if node.kind() == "declaration" {
18073                        break Some(node);
18074                    }
18075                    node = node.parent()?;
18076                }
18077            })
18078            .expect("declaration after the displaced terminator");
18079        let conditional = declaration
18080            .parent()
18081            .filter(|node| node.kind() == "preproc_ifdef")
18082            .expect("damaged inner conditional");
18083        let outer = conditional
18084            .parent()
18085            .filter(|node| node.kind() == "preproc_ifdef")
18086            .expect("ordinary outer include guard");
18087        let terminator = cpp_displaced_preprocessor_terminator(conditional)
18088            .expect("structured displaced #endif");
18089        assert_eq!(node_text(terminator, damaged), "#endif");
18090        assert!(terminator.end_byte() <= declaration.start_byte());
18091        assert!(!preprocessor_conditional_contains_descendant(
18092            conditional,
18093            declaration
18094        ));
18095        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
18096        assert!(preprocessor_conditional_contains_descendant(
18097            outer,
18098            declaration
18099        ));
18100
18101        let tree = parse(guarded);
18102        let conditional = tree
18103            .root_node()
18104            .named_child(0)
18105            .filter(|node| node.kind() == "preproc_ifdef")
18106            .expect("ordinary conditional");
18107        let declaration = conditional
18108            .named_children(&mut conditional.walk())
18109            .find(|node| node.kind() == "declaration")
18110            .expect("guarded declaration");
18111        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18112        assert!(preprocessor_conditional_contains_descendant(
18113            conditional,
18114            declaration
18115        ));
18116
18117        let damaged_alternative = format!(
18118            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
18119            "UNUSED(value)\n".repeat(64)
18120        );
18121        let tree = parse(&damaged_alternative);
18122        let conditional = tree
18123            .root_node()
18124            .named_child(0)
18125            .filter(|node| node.kind() == "preproc_ifdef")
18126            .expect("outer conditional with an alternative");
18127        assert!(conditional.has_error());
18128        assert!(conditional.child_by_field_name("alternative").is_some());
18129        assert!(
18130            conditional
18131                .child(conditional.child_count() - 1)
18132                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
18133        );
18134        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
18135
18136        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";
18137        let tree = parse(split_declaration);
18138        let root = tree.root_node();
18139        let conditional = root
18140            .named_children(&mut root.walk())
18141            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
18142            .expect("split declaration conditional");
18143        let target = split_declaration
18144            .find("static int target")
18145            .expect("target byte");
18146        let boundary =
18147            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
18148        assert!(boundary.end_byte <= target, "{boundary:?}");
18149        assert_eq!(boundary.end_line, 9, "{boundary:?}");
18150        let target_node = root
18151            .descendant_for_byte_range(target, target + "static".len())
18152            .expect("target node");
18153        assert!(!preprocessor_conditional_contains_descendant(
18154            conditional,
18155            target_node
18156        ));
18157    }
18158
18159    #[test]
18160    fn fragmented_reference_guard_is_recovered() {
18161        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";
18162        let mut parser = Parser::new();
18163        parser
18164            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18165            .expect("C++ grammar");
18166        let tree = parser.parse(source, None).expect("fixture tree");
18167        let start = source.rfind("helper").expect("reference byte");
18168        let node = tree
18169            .root_node()
18170            .descendant_for_byte_range(start, start + "helper".len())
18171            .expect("reference node");
18172        let mut expected = HashSet::default();
18173        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
18174            vec![
18175                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
18176                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
18177            ],
18178        )));
18179        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
18180    }
18181
18182    #[test]
18183    fn expression_defined_and_ifndef_guards_are_incompatible() {
18184        let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
18185        let mut parser = Parser::new();
18186        parser
18187            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18188            .expect("C++ grammar");
18189        let tree = parser.parse(source, None).expect("fixture tree");
18190        let root = tree.root_node();
18191        let selected_start = source.find("selected").expect("selected declaration");
18192        let rejected_start = source.find("rejected").expect("rejected declaration");
18193        let selected = root
18194            .descendant_for_byte_range(selected_start, selected_start + "selected".len())
18195            .expect("selected node");
18196        let rejected = root
18197            .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
18198            .expect("rejected node");
18199        let selected_guards =
18200            preprocessor_guard_environment(selected, source).expect("selected guards");
18201        let rejected_guards =
18202            preprocessor_guard_environment(rejected, source).expect("rejected guards");
18203
18204        assert!(
18205            merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
18206            "opposite spellings of one macro guard must contradict"
18207        );
18208    }
18209
18210    #[test]
18211    fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
18212        let source = r#"#ifdef _WIN32
18213#if defined(__cplusplus)
18214extern "C"
18215#endif
18216int platform_api(void);
18217#endif
18218
18219#ifdef _WIN32
18220static int entropy_target(void) { return 0; }
18221#else
18222#ifdef HAVE_COMMON_RANDOM
18223static int other_target(void) { return 0; }
18224#elif defined(HAVE_GETENTROPY)
18225static int entropy_target(void) { return 1; }
18226static int use_entropy(void) { return entropy_target(); }
18227#endif
18228#endif
18229"#;
18230        let mut parser = Parser::new();
18231        parser
18232            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18233            .expect("C++ grammar");
18234        let tree = parser.parse(source, None).expect("fixture tree");
18235        let start = source.rfind("entropy_target()").expect("reference");
18236        let node = tree
18237            .root_node()
18238            .descendant_for_byte_range(start, start + "entropy_target".len())
18239            .expect("reference node");
18240        let guards = preprocessor_guard_environment(node, source).expect("active C branch");
18241        assert!(
18242            guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
18243            "{guards:#?}"
18244        );
18245        assert!(
18246            guards.contains(&PreprocessorGuard::Undefined(
18247                "HAVE_COMMON_RANDOM".to_string()
18248            )),
18249            "{guards:#?}"
18250        );
18251        assert!(
18252            guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
18253            "{guards:#?}"
18254        );
18255        assert!(
18256            !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
18257            "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
18258        );
18259    }
18260
18261    #[test]
18262    fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
18263        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";
18264        let mut parser = Parser::new();
18265        parser
18266            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18267            .expect("C++ grammar");
18268        let tree = parser.parse(source, None).expect("fixture tree");
18269        let root = tree.root_node();
18270        let node_at = |text: &str, start: usize| {
18271            root.descendant_for_byte_range(start, start + text.len())
18272                .expect("token node")
18273        };
18274
18275        let key_start = source.find("case KEY").expect("case label") + "case ".len();
18276        let guard_start = source.find("ENABLE_KEYS").expect("guard name");
18277        assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
18278        assert!(!is_ordinary_macro_reference_node(node_at(
18279            "ENABLE_KEYS",
18280            guard_start,
18281        )));
18282    }
18283
18284    #[test]
18285    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
18286        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";
18287        let mut parser = Parser::new();
18288        parser
18289            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18290            .expect("C++ grammar");
18291        let tree = parser.parse(source, None).expect("fixture tree");
18292        let root = tree.root_node();
18293        let definition_start = source.find("target(void)").expect("definition");
18294        let reference_start = source.rfind("target()").expect("reference");
18295        let definition = root
18296            .descendant_for_byte_range(definition_start, definition_start + "target".len())
18297            .expect("definition node");
18298        let reference = root
18299            .descendant_for_byte_range(reference_start, reference_start + "target".len())
18300            .expect("reference node");
18301        let required =
18302            preprocessor_guard_environment(definition, source).expect("definition guard");
18303        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
18304        assert!(guard_requirements_hold_at_reference(
18305            &required,
18306            Some(&active)
18307        ));
18308    }
18309
18310    #[test]
18311    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
18312        let source = "g_autoptr(FuChunkArray) self = make_array();";
18313        let mut parser = Parser::new();
18314        parser
18315            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18316            .expect("C++ grammar");
18317        let tree = parser.parse(source, None).expect("fixture tree");
18318        let statement = tree.root_node().named_child(0).expect("statement");
18319        let binding =
18320            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
18321        assert_eq!(binding.name, "self");
18322        assert_eq!(binding.type_name, "FuChunkArray");
18323        assert_eq!(binding.pointer_depth, 1);
18324
18325        let near_miss = "holder(FuChunkArray) self = make_array();";
18326        let tree = parser.parse(near_miss, None).expect("near-miss tree");
18327        let statement = tree.root_node().named_child(0).expect("statement");
18328        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
18329    }
18330
18331    #[test]
18332    fn boolean_guard_normalization_proves_equivalence_and_implication() {
18333        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
18334        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
18335        let negated_windows_branch =
18336            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
18337        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
18338        assert_eq!(negated_windows_branch, portable);
18339
18340        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
18341        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
18342        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
18343        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
18344        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
18345        assert!(fallback_branch.implies(&fallback_declaration));
18346        assert!(
18347            BooleanGuardExpression::Truthy("FEATURE".to_string())
18348                .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
18349        );
18350        assert!(
18351            BooleanGuardExpression::Undefined("FEATURE".to_string())
18352                .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
18353        );
18354        assert!(
18355            !BooleanGuardExpression::Defined("FEATURE".to_string())
18356                .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
18357        );
18358        assert!(!fallback_declaration.implies(&fallback_branch));
18359    }
18360
18361    #[test]
18362    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
18363        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";
18364        let mut parser = Parser::new();
18365        parser
18366            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18367            .expect("C++ grammar");
18368        let tree = parser.parse(source, None).expect("fixture tree");
18369        let root = tree.root_node();
18370        let call = |marker: &str| {
18371            let start = source.find(marker).expect("call marker");
18372            let mut node = root
18373                .descendant_for_byte_range(start, start + "helper".len())
18374                .expect("call name node");
18375            loop {
18376                if node.kind() == "call_expression" {
18377                    break node;
18378                }
18379                node = node.parent().expect("call expression ancestor");
18380            }
18381        };
18382        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
18383        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
18384        let keyword_call = call("helper(NULL, template); /* bound */");
18385        let keyword_arguments = keyword_call
18386            .child_by_field_name("arguments")
18387            .expect("keyword argument list");
18388        assert_eq!(
18389            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
18390            1
18391        );
18392        assert_eq!(
18393            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
18394            0
18395        );
18396
18397        let unbound_call = call("helper(NULL, template); /* unbound */");
18398        let unbound_arguments = unbound_call
18399            .child_by_field_name("arguments")
18400            .expect("unbound argument list");
18401        assert_eq!(
18402            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
18403            0
18404        );
18405    }
18406
18407    #[test]
18408    fn c_function_declarator_recovery_accepts_invocations_not_binders() {
18409        let source = r#"#define MAKE(type) type *value
18410MAKE(int *);
18411typedef struct Item Item;
18412struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
18413void gen_op_movl(void *s, int first, int second) { }
18414const char *strZ(const char *value) { return value; }
18415int body(void *s) {
18416    MAKE(int *);
18417    gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
18418                offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
18419    execvp(strZ(value), UNCONSTIFY(char **, args));
18420}
18421 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
18422    if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
18423int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
18424    if (d->units) {
18425        switch (target->nodetype) {
18426        case 1:
18427        case 2:
18428            break;
18429        default:
18430            AMEND_WRONG_NODETYPE("deviation", "replace", "units");
18431        }
18432        DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
18433        lysdict_remove(ctx, ((struct Item *)target)->units);
18434        DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
18435    }
18436     return 0;
18437 }
18438STATIC EFI_STATUS Encode () { return 0; }
18439"#;
18440        let tree = parse_cpp(source);
18441        let top_macro_start = source.find("MAKE(int *);").expect("top macro");
18442        let top_macro = tree
18443            .root_node()
18444            .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
18445            .expect("top macro node");
18446        let body_macro_start = source
18447            .match_indices("MAKE(int *);")
18448            .nth(1)
18449            .expect("body macro")
18450            .0;
18451        let body_macro = tree
18452            .root_node()
18453            .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
18454            .expect("body macro node");
18455        let function_call_start = source
18456            .find("gen_op_movl(s, offsetof(CPUX86State")
18457            .expect("function call");
18458        let function_call = tree
18459            .root_node()
18460            .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
18461            .expect("function call node");
18462        let strz_start = source.find("strZ(value)").expect("nested function call");
18463        let strz = tree
18464            .root_node()
18465            .named_descendant_for_byte_range(strz_start, strz_start + 4)
18466            .expect("nested function call node");
18467        let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
18468        let recovered_call = tree
18469            .root_node()
18470            .named_descendant_for_byte_range(
18471                recovered_call_start,
18472                recovered_call_start + "lysdict_remove".len(),
18473            )
18474            .expect("recovered call node");
18475        let binder_start = source.find("Encode").expect("binder");
18476        let binder = tree
18477            .root_node()
18478            .named_descendant_for_byte_range(binder_start, binder_start + 6)
18479            .expect("binder node");
18480
18481        assert!(recovered_c_function_declarator_invocation(top_macro));
18482        assert!(recovered_c_function_declarator_invocation(body_macro));
18483        assert!(recovered_c_function_declarator_invocation(function_call));
18484        assert!(recovered_c_function_declarator_invocation(strz));
18485        assert!(recovered_c_function_declarator_invocation(recovered_call));
18486        assert!(!recovered_c_function_declarator_invocation(binder));
18487    }
18488
18489    #[test]
18490    fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
18491        let source = r#"typedef int krb5_context;
18492int helper(int first, int second) { return first + second; }
18493static krb5_context ctx;
18494int main(int argc, char **argv) {
18495    int ccinitial;
18496    const char *collection_name, *typename;
18497    typename = helper(ctx, ccinitial);
18498    return 0;
18499}
18500"#;
18501        let tree = parse_cpp(source);
18502        let ctx = tree
18503            .root_node()
18504            .descendant_for_byte_range(
18505                source.find("ctx, ccinitial").expect("ctx argument"),
18506                source.find("ctx, ccinitial").expect("ctx argument") + 3,
18507            )
18508            .expect("ctx node");
18509        let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
18510        let ccinitial = tree
18511            .root_node()
18512            .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
18513            .expect("sibling node");
18514        let typename = named_node_at(&tree, source, "typename = helper");
18515        let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
18516
18517        assert_eq!(ctx.kind(), "identifier");
18518        assert!(recovered_c_parenthesized_declarator_reference(ctx));
18519        assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
18520        assert!(!recovered_c_parenthesized_declarator_reference(typename));
18521        assert!(!recovered_c_parenthesized_declarator_reference(helper));
18522    }
18523
18524    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
18525        let mut parser = Parser::new();
18526        parser
18527            .set_language(&tree_sitter_cpp::LANGUAGE.into())
18528            .expect("C++ grammar");
18529        let tree = parser.parse(source, None).expect("C++ fixture tree");
18530        let mut stack = vec![tree.root_node()];
18531        while let Some(node) = stack.pop() {
18532            if node.kind() == "enum_specifier" {
18533                return flattened_macro_namespace_components(node, source);
18534            }
18535            let mut cursor = node.walk();
18536            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
18537            stack.extend(children.into_iter().rev());
18538        }
18539        None
18540    }
18541
18542    #[test]
18543    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
18544        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18545namespace detail
18546{
18547enum class value_t { null };
18548}
18549NLOHMANN_JSON_NAMESPACE_END
18550NLOHMANN_JSON_NAMESPACE_BEGIN
18551namespace next
18552{
18553struct next_type {};
18554}
18555NLOHMANN_JSON_NAMESPACE_END
18556"#;
18557        assert_eq!(
18558            first_enum_flattened_namespace(complete),
18559            Some(vec!["detail".to_string()])
18560        );
18561
18562        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
18563        assert_eq!(
18564            first_enum_flattened_namespace(&stale_end),
18565            Some(vec!["detail".to_string()]),
18566            "a stale end marker before the begin marker must not replace the intended namespace"
18567        );
18568
18569        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
18570namespace detail
18571{
18572enum class value_t { null };
18573}
18574struct next_type {};
18575"#;
18576        assert_eq!(first_enum_flattened_namespace(incomplete), None);
18577    }
18578}
18579
18580/// Comparator laws for the total C++ lookup order introduced by #1876.
18581///
18582/// `sort_lookup_units` is the single tie-break the C++ resolver applies before
18583/// any "first wins" selection (template families in #1836, the visible
18584/// identifier index, the type-candidate lists). If its comparator is not a
18585/// total order over CodeUnit identity, some pair stays tied and the survivor
18586/// falls back to the order the units arrived in -- which is FxHash iteration
18587/// order over keys whose hash covers the absolute workspace root. That is the
18588/// exact mechanism behind #1836 and the #414 / #432 heisenbug, so the laws are
18589/// checked generatively rather than on one hand-picked list.
18590///
18591/// CodeUnit identity is `source`, `kind`, `fq`, `package_segment_count`,
18592/// `signature` and `synthetic` (see `impl PartialEq for CodeUnit`); a CodeUnit
18593/// carries no range, so declaration ranges are covered by the workspace-level
18594/// property in `tests/suite_analyzers/determinism_properties.rs` instead.
18595#[cfg(test)]
18596mod lookup_order_properties {
18597    use super::*;
18598    use proptest::prelude::*;
18599
18600    /// Segment spellings the C++ extractor and the shared renderer actually
18601    /// produce, including the `$`-joined nested spellings and non-ASCII
18602    /// identifiers that a byte-wise comparison has to keep apart.
18603    const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
18604    const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
18605    /// Two roots so the order is pinned across workspaces as well as inside
18606    /// one: the root path is precisely the byte string that used to leak into
18607    /// iteration order.
18608    const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
18609    const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
18610    const KINDS: [CodeUnitType; 6] = [
18611        CodeUnitType::Class,
18612        CodeUnitType::Function,
18613        CodeUnitType::Field,
18614        CodeUnitType::Module,
18615        CodeUnitType::Macro,
18616        CodeUnitType::FileScope,
18617    ];
18618
18619    /// Where one unit sits relative to another under the comparator that
18620    /// `sort_lookup_units` owns.
18621    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
18622    enum ProbedOrder {
18623        Before,
18624        Tied,
18625        After,
18626        /// Both directions reported "strictly first": the comparator is not
18627        /// dual, and no sort over it can be order-independent.
18628        Contradictory,
18629    }
18630
18631    impl ProbedOrder {
18632        fn mirror(self) -> Self {
18633            match self {
18634                ProbedOrder::Before => ProbedOrder::After,
18635                ProbedOrder::After => ProbedOrder::Before,
18636                other => other,
18637            }
18638        }
18639
18640        /// -1 / 0 / +1, so transitivity reads as the `<= 0` law.
18641        fn signum(self) -> i8 {
18642            match self {
18643                ProbedOrder::Before => -1,
18644                ProbedOrder::Tied => 0,
18645                ProbedOrder::After => 1,
18646                ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
18647            }
18648        }
18649    }
18650
18651    /// Read the comparator through its only caller.
18652    ///
18653    /// `sort_lookup_units` is a stable sort, so for a two-element slice the
18654    /// output says exactly whether the comparator put the second element
18655    /// strictly first. Sorting both arrangements of one pair therefore reports
18656    /// the comparator's verdict in both directions, including the contradictory
18657    /// case a single sort would hide.
18658    fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
18659        if left == right {
18660            // A stable sort cannot distinguish two equal values, and `Equal` is
18661            // the only verdict a total order can give them.
18662            return ProbedOrder::Tied;
18663        }
18664        let mut forward = vec![left.clone(), right.clone()];
18665        sort_lookup_units(&mut forward);
18666        let mut backward = vec![right.clone(), left.clone()];
18667        sort_lookup_units(&mut backward);
18668        let left_first = backward[0] == *left;
18669        let right_first = forward[0] == *right;
18670        match (left_first, right_first) {
18671            (true, true) => ProbedOrder::Contradictory,
18672            (true, false) => ProbedOrder::Before,
18673            (false, true) => ProbedOrder::After,
18674            (false, false) => ProbedOrder::Tied,
18675        }
18676    }
18677
18678    /// `(kind, text)` per segment. `CodeUnit`'s own `Debug` prints interned
18679    /// segment IDs, which are process-local and say nothing about a failure.
18680    fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
18681        let interner = segment_interner();
18682        unit.fq()
18683            .segments()
18684            .iter()
18685            .map(|&id| {
18686                let (text, kind) = interner.resolve(id);
18687                (kind.name(), text)
18688            })
18689            .collect()
18690    }
18691
18692    fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
18693        (
18694            0..ROOT_NAMES.len(),
18695            0..REL_PATHS.len(),
18696            0..KINDS.len(),
18697            prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
18698            0..3usize,
18699            0..SIGNATURES.len(),
18700            any::<bool>(),
18701        )
18702            .prop_map(
18703                |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
18704                    let source = ProjectFile::new(
18705                        std::env::temp_dir().join(ROOT_NAMES[root]),
18706                        REL_PATHS[rel_path],
18707                    );
18708                    let interner = segment_interner();
18709                    let mut fq = FqName::new();
18710                    for (atom, segment_kind) in &segments {
18711                        fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
18712                    }
18713                    // `from_fq` requires a non-empty declaration tail.
18714                    let package_segment_count = package_prefix % fq.len();
18715                    CodeUnit::from_fq(
18716                        source,
18717                        KINDS[kind],
18718                        fq,
18719                        package_segment_count,
18720                        SIGNATURES[signature].map(str::to_string),
18721                        synthetic,
18722                    )
18723                },
18724            )
18725    }
18726
18727    proptest! {
18728        #![proptest_config(ProptestConfig::with_cases(256))]
18729
18730        /// Reflexivity and duality: a unit ties with itself, and no pair is
18731        /// strictly first in both directions.
18732        #[test]
18733        fn lookup_order_is_reflexive_and_dual(
18734            left in code_unit_strategy(),
18735            right in code_unit_strategy(),
18736        ) {
18737            prop_assert_eq!(
18738                probe_order(&left, &left),
18739                ProbedOrder::Tied,
18740                "a unit must tie with itself: {:?}",
18741                left
18742            );
18743            let forward = probe_order(&left, &right);
18744            prop_assert_ne!(
18745                forward,
18746                ProbedOrder::Contradictory,
18747                "comparator put each of these strictly first: left={:?} right={:?}",
18748                left,
18749                right
18750            );
18751            prop_assert_eq!(
18752                probe_order(&right, &left),
18753                forward.mirror(),
18754                "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
18755                left,
18756                right
18757            );
18758        }
18759
18760        /// Transitivity: `a <= b` and `b <= c` imply `a <= c`.
18761        #[test]
18762        fn lookup_order_is_transitive(
18763            a in code_unit_strategy(),
18764            b in code_unit_strategy(),
18765            c in code_unit_strategy(),
18766        ) {
18767            let ab = probe_order(&a, &b);
18768            let bc = probe_order(&b, &c);
18769            let ac = probe_order(&a, &c);
18770            for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
18771                prop_assert_ne!(
18772                    probed,
18773                    ProbedOrder::Contradictory,
18774                    "comparator is not dual over {}: a={:?} b={:?} c={:?}",
18775                    pair,
18776                    a,
18777                    b,
18778                    c
18779                );
18780            }
18781            if ab.signum() <= 0 && bc.signum() <= 0 {
18782                prop_assert!(
18783                    ac.signum() <= 0,
18784                    "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
18785                     a={:?} b={:?} c={:?}",
18786                    ab,
18787                    bc,
18788                    ac,
18789                    a,
18790                    b,
18791                    c
18792                );
18793            }
18794        }
18795
18796        /// The property #1876 exists for: only identical identities may tie.
18797        /// A tie between distinct units is the residual hash-order dependence.
18798        #[test]
18799        fn lookup_order_separates_distinct_identities(
18800            left in code_unit_strategy(),
18801            right in code_unit_strategy(),
18802        ) {
18803            if probe_order(&left, &right) == ProbedOrder::Tied {
18804                prop_assert_eq!(
18805                    &left,
18806                    &right,
18807                    "distinct identities tied, so their order is whatever order they \
18808                     arrived in: left_segments={:?} right_segments={:?}",
18809                    fq_segments(&left),
18810                    fq_segments(&right)
18811                );
18812            }
18813        }
18814
18815        /// The consequence the resolver relies on: the sorted list is a
18816        /// function of the SET of units, not of the order they were pushed in.
18817        #[test]
18818        fn lookup_sort_is_permutation_invariant(
18819            units in prop::collection::vec(code_unit_strategy(), 1..=8),
18820        ) {
18821            let mut sorted = units.clone();
18822            sort_lookup_units(&mut sorted);
18823            for rotation in 0..units.len() {
18824                for reversed in [false, true] {
18825                    let mut permuted = units.clone();
18826                    permuted.rotate_left(rotation);
18827                    if reversed {
18828                        permuted.reverse();
18829                    }
18830                    sort_lookup_units(&mut permuted);
18831                    prop_assert_eq!(
18832                        &permuted,
18833                        &sorted,
18834                        "sorting a permutation gave a different list \
18835                         (rotation={}, reversed={}): input={:?}",
18836                        rotation,
18837                        reversed,
18838                        units
18839                    );
18840                }
18841            }
18842        }
18843    }
18844}