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,
13};
14use crate::graph::CppGraphSource;
15use crate::graph::extractor::ScanCtx;
16use crate::graph::syntax::object_macro_replacement_type_references;
17use crate::graph_support::CppSource;
18use crate::imports::{
19    IncludeTargetIndex, include_paths as cpp_include_paths, resolve_include_targets_with_index,
20};
21use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
22use brokk_bifrost_core::analyzer::model::{
23    CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
24    CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect, StructuredTypeName,
25};
26use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
27use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
28use brokk_bifrost_core::analyzer::query_token::QueryToken;
29use brokk_bifrost_core::analyzer::tree_walk::{ParentIndex, node_for_exact_range};
30use brokk_bifrost_core::analyzer::usages::common::same_node;
31use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
32use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
33use brokk_bifrost_core::cancellation::CancellationToken;
34use brokk_bifrost_core::hash::{HashMap, HashSet};
35use std::borrow::Cow;
36#[cfg(any(test, feature = "test-support"))]
37use std::cell::Cell;
38use std::cell::OnceCell;
39use std::cmp::Ordering as CmpOrdering;
40use std::collections::BTreeSet;
41use std::hash::Hash;
42use std::sync::atomic::{AtomicUsize, Ordering};
43use std::sync::{Arc, Mutex, OnceLock, RwLock};
44use std::thread::ThreadId;
45use std::time::{Duration, Instant};
46use tree_sitter::{Node, Parser, Tree};
47
48#[cfg(any(test, feature = "test-support"))]
49thread_local! {
50    static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
51}
52
53#[derive(Clone, Copy, PartialEq, Eq)]
54pub enum TargetKind {
55    Type,
56    Constructor,
57    FreeFunction,
58    Method,
59    GlobalField,
60    MemberField,
61    Macro,
62}
63
64pub enum LexicalTypeResolution {
65    Resolved {
66        unit: CodeUnit,
67        components: Vec<String>,
68        candidates: Vec<CodeUnit>,
69    },
70    Ambiguous,
71    Missing,
72}
73
74#[derive(Clone, Copy)]
75enum TypeCandidateResolution<'a> {
76    Canonical,
77    PreserveAlias,
78    PreserveTarget(&'a CodeUnit),
79}
80
81/// Why a name did not reduce to one indexed type declaration.
82///
83/// The two answers are not interchangeable. `Ambiguous` means the index holds
84/// several declarations and the caller must choose; `Unresolvable` means the
85/// index holds none, which is a boundary the workspace cannot see past. A
86/// `using`/`typedef` alias to a template parameter or to a standard-library
87/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
88/// answer with an empty candidate list (#1828).
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90enum TypeCandidateFailure {
91    Ambiguous,
92    Unresolvable,
93}
94
95impl TypeCandidateFailure {
96    fn lexical_resolution(self) -> LexicalTypeResolution {
97        match self {
98            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
99            Self::Unresolvable => LexicalTypeResolution::Missing,
100        }
101    }
102}
103
104pub enum LexicalCallableValueResolution {
105    Type(CodeUnit),
106    FreeFunction(CodeUnit),
107    Ambiguous,
108    Missing,
109}
110
111pub enum UsingEnumMemberResolution {
112    Resolved { owner: CodeUnit, member: CodeUnit },
113    Ambiguous,
114    Missing,
115}
116
117pub enum NamespaceValueResolution {
118    Resolved,
119    Ambiguous,
120    Missing,
121}
122
123#[derive(Clone, Debug, PartialEq, Eq)]
124pub enum OrdinaryMacroReferenceResolution {
125    Resolved(CodeUnit),
126    Ambiguous,
127    Missing,
128}
129
130#[derive(Clone, Debug, PartialEq, Eq)]
131pub enum RecoveredCReferenceRanges {
132    Complete(Vec<Range>),
133    LimitExceeded,
134}
135
136pub fn resolve_namespace_value(
137    analyzer: &CppGraphSource<'_>,
138    visibility: &VisibilityIndex<'_>,
139    file: &ProjectFile,
140    namespace: &str,
141    name: &str,
142    before_byte: usize,
143) -> NamespaceValueResolution {
144    let mut matches = Vec::new();
145    for candidate in visibility.visible_identifier_candidates(file, name) {
146        if type_owner_of(analyzer, candidate).is_some()
147            || candidate.package_name() != namespace
148            || (candidate.source() == file
149                && !analyzer
150                    .ranges(candidate)
151                    .iter()
152                    .any(|range| range.start_byte < before_byte))
153            || matches
154                .iter()
155                .any(|existing| same_visible_symbol(existing, candidate))
156        {
157            continue;
158        }
159        matches.push(candidate.clone());
160        if matches.len() > 1 {
161            return NamespaceValueResolution::Ambiguous;
162        }
163    }
164    matches
165        .pop()
166        .map(|_| NamespaceValueResolution::Resolved)
167        .unwrap_or(NamespaceValueResolution::Missing)
168}
169
170pub(crate) struct ScopedUsingEnumOwners {
171    scopes: Vec<Vec<CodeUnit>>,
172}
173
174/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
175/// Cross-file and inherited class imports are deliberately not inferred without persisted
176/// evidence; a missing imported enumerator therefore remains unproven rather than being
177/// misresolved.
178pub(crate) struct SemanticUsingEnumOwners {
179    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
180    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
181}
182
183pub(crate) enum SemanticUsingEnumMemberResolution {
184    Class(UsingEnumMemberResolution),
185    Namespace(UsingEnumMemberResolution),
186    Missing,
187}
188
189impl SemanticUsingEnumOwners {
190    pub(crate) fn new() -> Self {
191        Self {
192            class_imports: HashMap::default(),
193            namespace_imports: HashMap::default(),
194        }
195    }
196
197    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
198        let imports = self.class_imports.entry(class).or_default();
199        if !imports
200            .iter()
201            .any(|existing| same_visible_symbol(existing, &enum_owner))
202        {
203            imports.push(enum_owner);
204        }
205    }
206
207    pub fn import_namespace(
208        &mut self,
209        namespace: Vec<String>,
210        declaration_byte: usize,
211        enum_owner: CodeUnit,
212    ) {
213        let imports = self.namespace_imports.entry(namespace).or_default();
214        if !imports
215            .iter()
216            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
217        {
218            imports.push((declaration_byte, enum_owner));
219        }
220    }
221
222    pub fn resolve_member(
223        &self,
224        visibility: &VisibilityIndex<'_>,
225        file: &ProjectFile,
226        class: Option<&CodeUnit>,
227        namespace: &[String],
228        before_byte: usize,
229        name: &str,
230    ) -> SemanticUsingEnumMemberResolution {
231        if let Some(class) = class
232            && let Some((_, imports)) = self
233                .class_imports
234                .iter()
235                .find(|(owner, _)| same_visible_symbol(owner, class))
236        {
237            let resolution =
238                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
239            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
240                return SemanticUsingEnumMemberResolution::Class(resolution);
241            }
242        }
243        for prefix_len in (0..=namespace.len()).rev() {
244            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
245                continue;
246            };
247            let owners = imports
248                .iter()
249                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
250                .map(|(_, owner)| owner);
251            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
252            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
253                return SemanticUsingEnumMemberResolution::Namespace(resolution);
254            }
255        }
256        SemanticUsingEnumMemberResolution::Missing
257    }
258}
259
260fn resolve_using_enum_member_for_owners<'a>(
261    visibility: &VisibilityIndex<'_>,
262    file: &ProjectFile,
263    owners: impl IntoIterator<Item = &'a CodeUnit>,
264    name: &str,
265) -> UsingEnumMemberResolution {
266    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
267    for owner in owners {
268        for member in visibility.visible_members_for_owner_name(file, owner, name) {
269            if !member.is_field()
270                || matches.iter().any(|(existing_owner, existing_member)| {
271                    same_visible_symbol(existing_owner, owner)
272                        && same_visible_symbol(existing_member, member)
273                })
274            {
275                continue;
276            }
277            matches.push((owner.clone(), member.clone()));
278        }
279    }
280    match matches.len() {
281        0 => UsingEnumMemberResolution::Missing,
282        1 => {
283            let (owner, member) = matches.pop().expect("one using-enum match");
284            UsingEnumMemberResolution::Resolved { owner, member }
285        }
286        _ => UsingEnumMemberResolution::Ambiguous,
287    }
288}
289
290impl ScopedUsingEnumOwners {
291    pub(crate) fn new() -> Self {
292        Self {
293            scopes: vec![Vec::new()],
294        }
295    }
296
297    pub fn enter_scope(&mut self) {
298        self.scopes.push(Vec::new());
299    }
300
301    pub fn exit_scope(&mut self) {
302        if self.scopes.len() > 1 {
303            self.scopes.pop();
304        }
305    }
306
307    pub fn import(&mut self, owner: CodeUnit) {
308        let scope = self
309            .scopes
310            .last_mut()
311            .expect("using-enum scope stack is never empty");
312        if !scope
313            .iter()
314            .any(|existing| same_visible_symbol(existing, &owner))
315        {
316            scope.push(owner);
317        }
318    }
319
320    pub fn resolve_member(
321        &self,
322        visibility: &VisibilityIndex<'_>,
323        file: &ProjectFile,
324        name: &str,
325    ) -> UsingEnumMemberResolution {
326        for scope in self.scopes.iter().rev() {
327            let resolution =
328                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
329            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
330                return resolution;
331            }
332        }
333        UsingEnumMemberResolution::Missing
334    }
335}
336
337#[derive(Clone)]
338pub struct TargetSpec {
339    pub target: CodeUnit,
340    pub kind: TargetKind,
341    pub owner: Option<CodeUnit>,
342    pub member_name: String,
343    pub callable_arity: Option<CallableArity>,
344    pub activated_callable_arities: Vec<ActivatedCallableArity>,
345    pub param_types: Option<Vec<String>>,
346    pub enum_owner_kind: EnumOwnerKind,
347    pub owner_is_forward_declaration: bool,
348    pub callable_has_definition_body: bool,
349}
350
351#[derive(Clone, Copy)]
352pub struct ActivatedCallableArity {
353    pub activation_byte: usize,
354    pub arity: CallableArity,
355}
356
357#[derive(Debug, PartialEq, Eq, Hash)]
358pub struct TypeScanKey {
359    target: LogicalSymbolKey,
360    member_name: String,
361}
362
363#[derive(Clone, Debug, PartialEq, Eq, Hash)]
364struct LogicalSymbolKey {
365    kind: CodeUnitType,
366    fq_name: String,
367    signature: Option<String>,
368}
369
370struct ResolvedTypeOwner {
371    unit: CodeUnit,
372    is_forward_declaration: bool,
373}
374
375#[derive(Clone, Copy, PartialEq, Eq)]
376pub enum EnumOwnerKind {
377    Scoped,
378    Unscoped,
379    NonEnum,
380}
381
382impl TargetSpec {
383    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
384        (self.kind == TargetKind::Type).then(|| TypeScanKey {
385            target: logical_symbol_key(&self.target),
386            member_name: self.member_name.clone(),
387        })
388    }
389
390    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
391        if target.is_class() {
392            return Some(Self::new(
393                target.clone(),
394                TargetKind::Type,
395                Some(target.clone()),
396                target.identifier().to_string(),
397                None,
398                None,
399            ));
400        }
401
402        if target.is_field() {
403            // A namespace (module) is not a receiver: a namespace-scoped constant such as
404            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
405            // qualified from outside, exactly like a global. Treating a module owner as a
406            // member-field owner makes the receiver/owner-context match reject every valid
407            // reference, so resolve it as a global field instead.
408            let owner = type_owner_of(analyzer, target);
409            let kind = if owner.is_some() {
410                TargetKind::MemberField
411            } else {
412                TargetKind::GlobalField
413            };
414            let enum_owner_kind = owner
415                .as_ref()
416                .map(|owner| classify_enum_owner(analyzer, owner))
417                .unwrap_or(EnumOwnerKind::NonEnum);
418            let mut spec = Self::new(
419                target.clone(),
420                kind,
421                owner,
422                target.identifier().to_string(),
423                None,
424                None,
425            );
426            spec.enum_owner_kind = enum_owner_kind;
427            return Some(spec);
428        }
429
430        if target.is_function() {
431            // Free functions declared inside a namespace have a module owner; that namespace is
432            // not a call receiver, so resolve them as free functions rather than methods.
433            let owner_resolution = target_type_owner_resolution(analyzer, target);
434            let owner_is_forward_declaration = owner_resolution
435                .as_ref()
436                .is_some_and(|owner| owner.is_forward_declaration);
437            let owner = owner_resolution.map(|owner| owner.unit);
438            let kind = if owner.as_ref().is_some_and(|owner| {
439                target.identifier() == owner.identifier()
440                    || analyzer
441                        .cpp
442                        .and_then(|cpp| cpp.template_metadata(owner))
443                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
444            }) {
445                TargetKind::Constructor
446            } else if owner.is_some() {
447                TargetKind::Method
448            } else {
449                TargetKind::FreeFunction
450            };
451            let mut spec = Self::new(
452                target.clone(),
453                kind,
454                owner,
455                target.identifier().to_string(),
456                Some(cpp_callable_arity(analyzer, target)),
457                cpp_callable_parameter_types(analyzer, target),
458            );
459            spec.owner_is_forward_declaration = owner_is_forward_declaration;
460            spec.callable_has_definition_body =
461                callable_target_has_definition_body(analyzer, target);
462            return Some(spec);
463        }
464
465        if target.is_macro() {
466            return Some(Self::new(
467                target.clone(),
468                TargetKind::Macro,
469                None,
470                target.identifier().to_string(),
471                None,
472                None,
473            ));
474        }
475
476        None
477    }
478
479    pub fn with_visible_callable_arities<'a>(
480        &'a self,
481        analyzer: &CppGraphSource<'_>,
482        cpp: &dyn CppSource,
483        visibility: &VisibilityIndex<'_>,
484        file: &ProjectFile,
485        prepared: &PreparedSyntaxTree,
486    ) -> Cow<'a, Self> {
487        let macro_parameter_arity =
488            visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
489        let activated_callable_arities =
490            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
491        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
492            return Cow::Borrowed(self);
493        }
494        let mut effective = self.clone();
495        if let Some(macro_parameter_arity) = macro_parameter_arity {
496            effective.callable_arity = Some(macro_parameter_arity);
497        }
498        effective.activated_callable_arities = activated_callable_arities;
499        Cow::Owned(effective)
500    }
501
502    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
503        let base = self.callable_arity?;
504        Some(
505            self.activated_callable_arities
506                .iter()
507                .filter(|candidate| candidate.activation_byte <= byte)
508                .fold(base, |arity, candidate| {
509                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
510                }),
511        )
512    }
513
514    pub fn new(
515        target: CodeUnit,
516        kind: TargetKind,
517        owner: Option<CodeUnit>,
518        member_name: String,
519        callable_arity: Option<CallableArity>,
520        param_types: Option<Vec<String>>,
521    ) -> Self {
522        Self {
523            target,
524            kind,
525            owner,
526            member_name,
527            callable_arity,
528            activated_callable_arities: Vec::new(),
529            param_types,
530            enum_owner_kind: EnumOwnerKind::NonEnum,
531            owner_is_forward_declaration: false,
532            callable_has_definition_body: false,
533        }
534    }
535}
536
537fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
538    let Some(cpp) = analyzer.cpp else {
539        return false;
540    };
541    let Some(prepared) = cpp.prepared_syntax(analyzer.token, target.source()) else {
542        return false;
543    };
544    analyzer.ranges(target).into_iter().any(|range| {
545        let end = range
546            .start_byte
547            .saturating_add(1)
548            .min(prepared.source().len());
549        let mut current = prepared
550            .tree()
551            .root_node()
552            .descendant_for_byte_range(range.start_byte, end);
553        while let Some(node) = current {
554            match node.kind() {
555                "function_definition" => return true,
556                "declaration" => return false,
557                _ => current = node.parent(),
558            }
559        }
560        false
561    })
562}
563
564fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
565    LogicalSymbolKey {
566        kind: unit.kind(),
567        fq_name: unit.fq_name(),
568        signature: unit.signature().map(str::to_string),
569    }
570}
571
572fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
573    let classify = |source: &str| {
574        let source = source.trim_start();
575        if source.starts_with("enum class ") || source.starts_with("enum struct ") {
576            Some(EnumOwnerKind::Scoped)
577        } else if source.starts_with("enum ") {
578            Some(EnumOwnerKind::Unscoped)
579        } else {
580            None
581        }
582    };
583    owner
584        .signature()
585        .and_then(classify)
586        .or_else(|| {
587            analyzer
588                .get_source(owner, false)
589                .as_deref()
590                .and_then(classify)
591        })
592        .unwrap_or(EnumOwnerKind::NonEnum)
593}
594
595#[derive(Clone, PartialEq, Eq, Hash)]
596pub struct CppScanBinding {
597    pub unit: Option<CodeUnit>,
598    pub type_name: Option<String>,
599    pub indirection: i32,
600}
601
602impl CppScanBinding {
603    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
604        Self {
605            type_name: Some(cpp_name_for(&unit)),
606            unit: Some(unit),
607            indirection,
608        }
609    }
610
611    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
612        Self {
613            type_name: Some(type_name),
614            unit,
615            indirection,
616        }
617    }
618
619    pub fn as_arg_type(&self) -> Option<CppArgType> {
620        let name = self
621            .type_name
622            .clone()
623            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
624        Some(CppArgType {
625            name,
626            unit: self.unit.clone(),
627            indirection: self.indirection,
628            pointee_const: false,
629        })
630    }
631}
632
633type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
634pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
635pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
636type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
637pub type MacroEnvironmentCursorCell = Arc<Mutex<MacroEnvironmentCursor>>;
638type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
639type MacroLocalBindingTemplateCache =
640    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
641type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
642
643#[derive(Clone, Default)]
644pub struct MacroEnvironment {
645    bindings: HashMap<String, MacroBinding>,
646    known_undefined_names: HashSet<String>,
647    /// Names the translation unit's compile command proves defined (#2011):
648    /// the `-D`s that survive command ordering, intersected across every
649    /// configuration naming the TU. Seeded once at TU start. An explicit
650    /// `#undef` seen later lands in `known_undefined_names` and wins.
651    build_proven_defines: HashSet<String>,
652    unknown_names: bool,
653    applied_pragma_once_files: HashSet<ProjectFile>,
654    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
655}
656
657#[derive(Default)]
658pub struct MacroEnvironmentCursor {
659    frontier: usize,
660    environment: Arc<MacroEnvironment>,
661}
662
663impl MacroEnvironment {
664    fn binding(&self, name: &str) -> Option<&MacroBinding> {
665        self.bindings.get(name)
666    }
667
668    fn may_bind(&self, name: &str) -> bool {
669        self.bindings.contains_key(name) || self.unknown_names
670    }
671
672    fn insert(&mut self, name: String, binding: MacroBinding) {
673        self.known_undefined_names.remove(&name);
674        self.bindings.insert(name, binding);
675    }
676
677    fn remove(&mut self, name: &str) {
678        self.bindings.remove(name);
679        self.known_undefined_names.insert(name.to_string());
680    }
681
682    fn remove_known_undefined(&mut self, name: &str) {
683        self.known_undefined_names.remove(name);
684    }
685
686    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
687        for binding in self.bindings.values_mut() {
688            *binding = MacroBinding::uncertain_from(binding, source, byte);
689        }
690        self.known_undefined_names.clear();
691        // An untracked include could `#undef` a command-line define, so the
692        // may-hold filter must stop treating the build facts as decisive from
693        // here on. The additive proof path keeps its facts: they still hold at
694        // the include chain's activation point.
695        self.build_proven_defines.clear();
696        self.unknown_names = true;
697    }
698
699    fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
700        guards.iter().all(|guard| self.guard_may_hold(guard))
701    }
702
703    fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
704        let Some(expression) = guard.as_boolean_expression() else {
705            return true;
706        };
707        self.boolean_guard_may_hold(&expression)
708    }
709
710    fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
711        match expression {
712            BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
713            BooleanGuardExpression::Undefined(name) => {
714                self.bindings
715                    .get(name)
716                    .is_none_or(|binding| !binding.is_exact())
717                    && (!self.build_proven_defines.contains(name)
718                        || self.known_undefined_names.contains(name))
719            }
720            BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
721            BooleanGuardExpression::Opaque(_)
722            | BooleanGuardExpression::NegatedOpaque(_)
723            | BooleanGuardExpression::Constant(true) => true,
724            BooleanGuardExpression::Constant(false) => false,
725            BooleanGuardExpression::All(expressions) => expressions
726                .iter()
727                .all(|expression| self.boolean_guard_may_hold(expression)),
728            BooleanGuardExpression::Any(expressions) => expressions
729                .iter()
730                .any(|expression| self.boolean_guard_may_hold(expression)),
731        }
732    }
733}
734
735#[derive(Clone)]
736pub enum EffectiveUsingTarget {
737    Ordinary {
738        name: String,
739        target_components: Vec<String>,
740        global: bool,
741    },
742    Namespace {
743        namespace_components: Vec<String>,
744        global: bool,
745    },
746}
747
748#[derive(Clone)]
749pub struct OrdinaryTypeImport {
750    pub target: EffectiveUsingTarget,
751    pub source: ProjectFile,
752    pub declaration_byte: usize,
753    pub scope_start: usize,
754    pub scope_end: usize,
755    pub scope_depth: usize,
756    pub block_scope: bool,
757    pub lexical_depth: usize,
758    pub declaration_namespace: Vec<String>,
759    pub namespace_scope: Option<Vec<String>>,
760    pub resolved_target_components: Option<Vec<String>>,
761    pub required_guards: HashSet<PreprocessorGuard>,
762}
763
764#[derive(Clone)]
765pub struct ConditionalIncludeProjection {
766    pub activation_byte: usize,
767    pub required_guards: HashSet<PreprocessorGuard>,
768}
769
770#[derive(Default)]
771pub struct SourceUsingIndex {
772    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
773    pub directives: Vec<OrdinaryTypeImport>,
774}
775
776#[derive(Default)]
777pub struct ProjectUsingIndex {
778    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
779    pub directives: Vec<OrdinaryTypeImport>,
780}
781
782type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
783
784pub struct EffectiveUsingIndex {
785    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
786}
787
788impl EffectiveUsingIndex {
789    fn new(_root: ProjectFile) -> Self {
790        Self {
791            projected_by_name: Mutex::new(HashMap::default()),
792        }
793    }
794
795    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
796        self.projected_by_name
797            .lock()
798            .expect("C++ effective-using projection cache poisoned")
799            .entry(name.to_string())
800            .or_default()
801            .clone()
802    }
803}
804
805pub enum OrdinaryTypeImportResolution {
806    Resolved {
807        target: CodeUnit,
808        target_components: Vec<String>,
809        lexical_depth: usize,
810        is_direct: bool,
811    },
812    Ambiguous {
813        lexical_depth: usize,
814    },
815    Missing,
816}
817
818type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
819type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
820type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
821type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
822type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
823type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
824type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
825
826/// One callable declaration's inputs to [`VisibilityIndex::same_logical_callable`],
827/// read from its declaration syntax rather than from its persisted signature
828/// string: the comparable shape of each parameter, and the trailing identity
829/// suffix that shape does not carry.
830struct ExtractedComparable {
831    shapes: Vec<CppComparableSlot>,
832    suffix: String,
833}
834
835/// How many alias hops [`VisibilityIndex::same_logical_callable`] follows
836/// before giving up on a written type name. A visited set already stops a
837/// cycle; this stops an adversarially long chain from costing a lookup per hop.
838const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
839
840/// Per-query C++ visibility facts.
841///
842/// The analyzer is *borrowed*, never cloned: `TreeSitterAnalyzer::clone` gives
843/// the clone a fresh, empty `QueryReadCache` on purpose (clones cross
844/// generations and overlays, where another generation's hydrated states would
845/// be wrong). An index that owned a clone would therefore see an inactive read
846/// cache for every `prepared_syntax` call it makes, re-reading and re-parsing
847/// the same source from the store once per candidate instead of once per query
848/// — the #1175 blow-up, where one scan re-parsed a 4.8 MB generated header
849/// tens of thousands of times.
850pub struct VisibilityIndex<'a> {
851    cpp: &'a dyn CppSource,
852    /// Proof that the request scope the index was built under is still open.
853    /// The index is a per-query object whose lifetime is inside the scope's,
854    /// so carrying the token here instead of on ninety method signatures is
855    /// the same guarantee for far less plumbing (issue #2414 step 3).
856    token: QueryToken<'a>,
857    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
858    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
859    global_field_internal_linkage: HashMap<CodeUnit, bool>,
860    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
861    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
862    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
863    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
864    project_using_index: OnceLock<ProjectUsingIndex>,
865    callable_reference_specs:
866        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
867    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
868    compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
869    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
870    #[cfg(any(test, feature = "test-support"))]
871    conditional_include_projection_index_build_count: AtomicUsize,
872    #[cfg(any(test, feature = "test-support"))]
873    conditional_include_projection_state_count: AtomicUsize,
874    #[cfg(any(test, feature = "test-support"))]
875    conditional_include_target_state_count: AtomicUsize,
876    #[cfg(any(test, feature = "test-support"))]
877    include_activation_build_count: AtomicUsize,
878    #[cfg(any(test, feature = "test-support"))]
879    using_donor_activation_count: AtomicUsize,
880    #[cfg(any(test, feature = "test-support"))]
881    using_namespace_lookup_count: AtomicUsize,
882    #[cfg(any(test, feature = "test-support"))]
883    using_name_candidate_inspection_count: AtomicUsize,
884    #[cfg(any(test, feature = "test-support"))]
885    callable_reference_spec_build_count: AtomicUsize,
886    #[cfg(any(test, feature = "test-support"))]
887    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
888    #[cfg(any(test, feature = "test-support"))]
889    visible_parser_alias_name_set_build_count: AtomicUsize,
890    parser_alias_fallback_calls: AtomicUsize,
891    parser_alias_fallback_files: AtomicUsize,
892    parser_alias_source_parses: AtomicUsize,
893    parser_alias_fallback_elapsed_micros: AtomicUsize,
894    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
895    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
896    callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
897    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
898    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
899    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
900    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
901    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
902    // A forward cursor is useful only while its caller visits one source in byte order. The
903    // authoritative differential shares this index across target workers, whose frontiers can
904    // interleave arbitrarily, so sharing one cursor per file would serialize the include replay
905    // and repeatedly reset it. Keep one bounded cursor per participating worker instead; the
906    // immutable event and parse caches above remain shared.
907    pub macro_environment_cursors:
908        Mutex<HashMap<(ProjectFile, ThreadId), MacroEnvironmentCursorCell>>,
909    macro_replacements: Mutex<MacroReplacementCache>,
910    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
911    macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
912    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
913    #[cfg(any(test, feature = "test-support"))]
914    pub macro_replacement_parse_count: AtomicUsize,
915    #[cfg(any(test, feature = "test-support"))]
916    pub macro_event_application_count: AtomicUsize,
917    #[cfg(any(test, feature = "test-support"))]
918    pub macro_environment_copy_count: AtomicUsize,
919    #[cfg(any(test, feature = "test-support"))]
920    pub macro_environment_request_count: AtomicUsize,
921    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
922    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
923    #[cfg(any(test, feature = "test-support"))]
924    qualified_candidate_inspections: AtomicUsize,
925    #[cfg(any(test, feature = "test-support"))]
926    target_preserving_type_resolution_count: AtomicUsize,
927}
928
929impl Drop for VisibilityIndex<'_> {
930    fn drop(&mut self) {
931        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
932            return;
933        }
934        let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
935        if calls == 0 {
936            return;
937        }
938        eprintln!(
939            "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
940            calls,
941            self.parser_alias_fallback_files.load(Ordering::Relaxed),
942            self.parser_alias_source_parses.load(Ordering::Relaxed),
943            self.parser_alias_fallback_elapsed_micros
944                .load(Ordering::Relaxed)
945                / 1_000,
946        );
947    }
948}
949
950#[derive(Clone, Debug, PartialEq, Eq, Hash)]
951pub enum PreprocessorGuard {
952    Defined(String),
953    Undefined(String),
954    Boolean(BooleanGuardExpression),
955    Expression(String),
956    NegatedExpression(String),
957    Constant(bool),
958}
959
960#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
961pub enum BooleanGuardExpression {
962    Defined(String),
963    Undefined(String),
964    Truthy(String),
965    Falsy(String),
966    Opaque(String),
967    NegatedOpaque(String),
968    All(Vec<BooleanGuardExpression>),
969    Any(Vec<BooleanGuardExpression>),
970    Constant(bool),
971}
972
973impl BooleanGuardExpression {
974    fn negated(&self) -> Self {
975        match self {
976            Self::Defined(name) => Self::Undefined(name.clone()),
977            Self::Undefined(name) => Self::Defined(name.clone()),
978            Self::Truthy(name) => Self::Falsy(name.clone()),
979            Self::Falsy(name) => Self::Truthy(name.clone()),
980            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
981            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
982            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
983            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
984            Self::Constant(value) => Self::Constant(!value),
985        }
986    }
987
988    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
989        Self::normalized(expressions, true)
990    }
991
992    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
993        Self::normalized(expressions, false)
994    }
995
996    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
997        let mut normalized = Vec::new();
998        for expression in expressions {
999            match expression {
1000                Self::All(nested) if conjunction => normalized.extend(nested),
1001                Self::Any(nested) if !conjunction => normalized.extend(nested),
1002                Self::Constant(value) if value == conjunction => {}
1003                Self::Constant(value) => return Self::Constant(value),
1004                expression => normalized.push(expression),
1005            }
1006        }
1007        normalized.sort_unstable();
1008        normalized.dedup();
1009        match normalized.len() {
1010            0 => Self::Constant(conjunction),
1011            1 => normalized.pop().expect("one Boolean guard expression"),
1012            _ if conjunction => Self::All(normalized),
1013            _ => Self::Any(normalized),
1014        }
1015    }
1016
1017    fn implies(&self, required: &Self) -> bool {
1018        if self == required
1019            || matches!(self, Self::Constant(false))
1020            || matches!(required, Self::Constant(true))
1021        {
1022            return true;
1023        }
1024        if matches!(
1025            (self, required),
1026            (Self::Truthy(active), Self::Defined(required))
1027                | (Self::Undefined(active), Self::Falsy(required))
1028                if active == required
1029        ) {
1030            return true;
1031        }
1032        match self {
1033            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1034            Self::All(active) => match required {
1035                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1036                _ => active.iter().any(|expression| expression.implies(required)),
1037            },
1038            _ => match required {
1039                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1040                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1041                _ => false,
1042            },
1043        }
1044    }
1045
1046    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1047        match self {
1048            Self::Defined(name)
1049            | Self::Undefined(name)
1050            | Self::Truthy(name)
1051            | Self::Falsy(name) => name == macro_name,
1052            // Opaque expressions have structured conditional ownership but no
1053            // structured macro operands, so any mutation may change them.
1054            Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1055            Self::All(expressions) | Self::Any(expressions) => expressions
1056                .iter()
1057                .any(|expression| expression.may_depend_on_macro(macro_name)),
1058            Self::Constant(_) => false,
1059        }
1060    }
1061
1062    pub fn heap_size(&self) -> usize {
1063        match self {
1064            Self::Defined(value)
1065            | Self::Undefined(value)
1066            | Self::Truthy(value)
1067            | Self::Falsy(value)
1068            | Self::Opaque(value)
1069            | Self::NegatedOpaque(value) => value.len(),
1070            Self::All(expressions) | Self::Any(expressions) => {
1071                expressions
1072                    .iter()
1073                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1074                        size.saturating_add(std::mem::size_of::<Self>())
1075                            .saturating_add(expression.heap_size())
1076                    })
1077            }
1078            Self::Constant(_) => 0,
1079        }
1080    }
1081}
1082
1083impl PreprocessorGuard {
1084    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1085        match self {
1086            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1087            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1088            Self::Boolean(expression) => Some(expression.clone()),
1089            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1090            Self::Expression(_) | Self::NegatedExpression(_) => None,
1091        }
1092    }
1093
1094    fn negated(&self) -> Self {
1095        match self {
1096            Self::Defined(name) => Self::Undefined(name.clone()),
1097            Self::Undefined(name) => Self::Defined(name.clone()),
1098            Self::Boolean(expression) => Self::Boolean(expression.negated()),
1099            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1100            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1101            Self::Constant(value) => Self::Constant(!value),
1102        }
1103    }
1104
1105    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1106        match self {
1107            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1108            Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1109            // These expressions could not be lowered to a Boolean operand
1110            // tree, so their dependencies remain unknown.
1111            Self::Expression(_) | Self::NegatedExpression(_) => true,
1112            Self::Constant(_) => false,
1113        }
1114    }
1115}
1116
1117#[derive(Clone, PartialEq, Eq)]
1118pub enum MacroDefinition {
1119    Object {
1120        replacement: String,
1121    },
1122    Function {
1123        parameters: Vec<String>,
1124        replacement: String,
1125    },
1126    Unsupported,
1127}
1128
1129#[derive(Clone, Debug, PartialEq, Eq)]
1130pub enum MacroIncludeProtection {
1131    MacroGuard(String),
1132    PragmaOnce,
1133    None,
1134}
1135
1136enum ParsedMacroReplacement {
1137    Parsed { source: String, tree: Tree },
1138    Unsupported,
1139}
1140
1141/// The sentinel that gives a function-like macro replacement a parseable
1142/// statement context. The replacement text is copied in verbatim, so the only
1143/// bytes ahead of it are this prefix.
1144const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1145
1146/// A function-like macro replacement parsed inside a sentinel function body.
1147///
1148/// Tree-sitter keeps a `#define NAME(a) ...` replacement as one opaque
1149/// `preproc_arg`. Wrapping that exact byte slice in a function body recovers
1150/// its statements, declarations, and member calls as ordinary C++ structure.
1151/// The slice is copied verbatim at [`Self::body_offset`], so a node range in
1152/// [`Self::tree`] maps back onto the defining `preproc_arg` by subtracting
1153/// that offset.
1154pub struct ParsedReplacementBody {
1155    pub source: String,
1156    pub tree: Tree,
1157    pub body_offset: usize,
1158    pub parameters: Vec<String>,
1159}
1160
1161impl ParsedReplacementBody {
1162    /// The sentinel function body holding the replacement's statements.
1163    pub fn statements(&self) -> Option<Node<'_>> {
1164        first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1165            .child_by_field_name("body")
1166    }
1167
1168    /// The byte range `node` occupies in the file that defines the macro.
1169    ///
1170    /// `replacement_start` is the defining `preproc_arg`'s start byte. The
1171    /// replacement is copied into the sentinel verbatim, so subtracting the
1172    /// body offset and adding that start is exact.
1173    pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1174        debug_assert!(node.start_byte() >= self.body_offset);
1175        let start = replacement_start + (node.start_byte() - self.body_offset);
1176        start..start + (node.end_byte() - node.start_byte())
1177    }
1178
1179    /// Whether the replacement names the variadic argument pack.
1180    ///
1181    /// `__VA_ARGS__` parses as an ordinary identifier, so the sentinel tree
1182    /// gives no error for it even though the expansion it stands for is
1183    /// unknown at the definition. Reject it from the parsed tree rather than
1184    /// by scanning the replacement text.
1185    fn expands_variadic_arguments(&self) -> bool {
1186        let mut stack = vec![self.tree.root_node()];
1187        while let Some(node) = stack.pop() {
1188            if matches!(
1189                node.kind(),
1190                "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1191            ) && node_text(node, &self.source) == "__VA_ARGS__"
1192            {
1193                return true;
1194            }
1195            for index in (0..node.named_child_count()).rev() {
1196                if let Some(child) = node.named_child(index) {
1197                    stack.push(child);
1198                }
1199            }
1200        }
1201        false
1202    }
1203}
1204
1205fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1206    let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1207    let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1208        if compact.starts_with("0x") || compact.starts_with("0X") {
1209            (16, 2, |ch| ch.is_ascii_hexdigit())
1210        } else if compact.starts_with("0b") || compact.starts_with("0B") {
1211            (2, 2, |ch| matches!(ch, '0' | '1'))
1212        } else if compact.starts_with('0') && compact.len() > 1 {
1213            (8, 0, |ch| matches!(ch, '0'..='7'))
1214        } else {
1215            (10, 0, |ch| ch.is_ascii_digit())
1216        };
1217    let digit_len = compact[digits_start..]
1218        .chars()
1219        .take_while(|ch| digit_matches(*ch))
1220        .map(char::len_utf8)
1221        .sum::<usize>();
1222    if digit_len == 0 {
1223        return None;
1224    }
1225    let digits_end = digits_start + digit_len;
1226    if !compact[digits_end..]
1227        .chars()
1228        .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1229    {
1230        return None;
1231    }
1232    i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1233}
1234
1235#[derive(Clone)]
1236enum MacroLocalBindingTypeTemplate {
1237    Parameter(usize),
1238    Fixed(String),
1239}
1240
1241#[derive(Clone)]
1242struct MacroLocalBindingTemplate {
1243    name: String,
1244    declared_type: MacroLocalBindingTypeTemplate,
1245    pointer_depth: i32,
1246}
1247
1248/// A local declaration contributed by one structurally known function-like macro.
1249///
1250/// `type_node` points into the invocation syntax when the replacement's type
1251/// is one of the macro parameters. Consumers can therefore use their normal
1252/// lexical type resolver without parsing replacement text themselves.
1253pub struct MacroLocalBinding<'tree> {
1254    pub name: String,
1255    pub type_name: String,
1256    pub type_node: Option<Node<'tree>>,
1257    pub pointer_depth: i32,
1258}
1259
1260/// Recover GLib's `g_autoptr(T) name = value` declaration from the CST shape
1261/// produced by tree-sitter-cpp for C source. The grammar retains the macro
1262/// invocation as the assignment's left operand and the declared name as one
1263/// adjacent `ERROR(identifier)` node, so no macro text splitting is needed.
1264fn recognized_c_macro_declarator_binding<'tree>(
1265    statement: Node<'tree>,
1266    source: &str,
1267) -> Option<MacroLocalBinding<'tree>> {
1268    let assignment = match statement.kind() {
1269        "assignment_expression" => statement,
1270        "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1271        _ => return None,
1272    };
1273    if assignment.kind() != "assignment_expression" {
1274        return None;
1275    }
1276    let call = assignment.child_by_field_name("left")?;
1277    if call.kind() != "call_expression" {
1278        return None;
1279    }
1280    let function = call.child_by_field_name("function")?;
1281    if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1282        return None;
1283    }
1284    let arguments = call.child_by_field_name("arguments")?;
1285    let mut actuals = argument_children(arguments);
1286    let type_node = actuals.next()?;
1287    if actuals.next().is_some()
1288        || !matches!(
1289            type_node.kind(),
1290            "identifier"
1291                | "type_identifier"
1292                | "qualified_identifier"
1293                | "scoped_type_identifier"
1294                | "template_type"
1295        )
1296    {
1297        return None;
1298    }
1299    let name_node = (0..assignment.named_child_count())
1300        .filter_map(|index| assignment.named_child(index))
1301        .filter(|child| child.kind() == "ERROR")
1302        .filter_map(|error| {
1303            (error.named_child_count() == 1)
1304                .then(|| error.named_child(0))
1305                .flatten()
1306        })
1307        .find(|node| node.kind() == "identifier")?;
1308    let name = node_text(name_node, source).trim();
1309    let type_name = node_text(type_node, source).trim();
1310    if name.is_empty() || type_name.is_empty() {
1311        return None;
1312    }
1313    Some(MacroLocalBinding {
1314        name: name.to_string(),
1315        type_name: type_name.to_string(),
1316        type_node: Some(type_node),
1317        pointer_depth: 1,
1318    })
1319}
1320
1321#[derive(Clone, PartialEq, Eq)]
1322pub struct MacroBinding {
1323    source: ProjectFile,
1324    declaration_byte: usize,
1325    definition: MacroDefinition,
1326    exact: bool,
1327}
1328
1329impl MacroBinding {
1330    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1331        Self {
1332            source: source.clone(),
1333            declaration_byte,
1334            definition: MacroDefinition::Unsupported,
1335            exact: false,
1336        }
1337    }
1338
1339    fn is_exact(&self) -> bool {
1340        self.exact
1341    }
1342
1343    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1344        Self {
1345            source: source.clone(),
1346            declaration_byte,
1347            definition: current.definition.clone(),
1348            exact: false,
1349        }
1350    }
1351}
1352
1353#[derive(Clone)]
1354pub enum MacroEvent {
1355    Define {
1356        name: String,
1357        binding: MacroBinding,
1358        byte: usize,
1359        conditional: bool,
1360    },
1361    Undef {
1362        name: String,
1363        byte: usize,
1364        conditional: bool,
1365    },
1366    Include {
1367        targets: Vec<ProjectFile>,
1368        byte: usize,
1369        conditional: bool,
1370    },
1371    Invalidate {
1372        byte: usize,
1373    },
1374}
1375
1376impl MacroEvent {
1377    pub fn byte(&self) -> usize {
1378        match self {
1379            Self::Define { byte, .. }
1380            | Self::Undef { byte, .. }
1381            | Self::Include { byte, .. }
1382            | Self::Invalidate { byte } => *byte,
1383        }
1384    }
1385}
1386
1387#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1388pub enum CallArityEvidence {
1389    Exact(usize),
1390    Unknown,
1391}
1392
1393impl CallArityEvidence {
1394    pub fn exact(self) -> Option<usize> {
1395        match self {
1396            Self::Exact(arity) => Some(arity),
1397            Self::Unknown => None,
1398        }
1399    }
1400
1401    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1402        self.exact().map(|arity| expected.accepts(arity))
1403    }
1404}
1405
1406#[derive(Clone)]
1407struct DeclaredFieldTypeFact {
1408    type_text: String,
1409    indirection: i32,
1410    template_arguments: Option<Vec<CppTemplateExpression>>,
1411}
1412
1413#[derive(Clone, PartialEq, Eq)]
1414enum StructuredAliasTarget {
1415    Builtin,
1416    Named {
1417        components: Vec<String>,
1418        global: bool,
1419        arguments: Option<Vec<CppTemplateExpression>>,
1420    },
1421}
1422
1423struct CppAlias {
1424    name: String,
1425    target: String,
1426    namespace: Option<String>,
1427}
1428
1429type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1430
1431/// Why template-argument resolution failed. Definition diagnostics render
1432/// each mode differently; graph scans only care that the resolution is
1433/// unproven and match `Err(_)`.
1434#[derive(Debug, Clone, PartialEq, Eq)]
1435pub enum CppTemplateResolutionError {
1436    /// A template alias expansion revisited `alias`.
1437    AliasCycle { alias: CodeUnit },
1438    /// The explicit arguments do not bind to the declared template parameters.
1439    ArgumentBinding,
1440    /// Bound arguments do not substitute into the alias target's arguments.
1441    Substitution,
1442    /// No visible primary template declaration could be selected and
1443    /// reconciled for the specialization family.
1444    PrimarySelection,
1445    /// More than one applicable specialization remains and none is strictly
1446    /// more specialized than every other candidate.
1447    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1448}
1449
1450/// The ambiguity candidates, deduplicated to one representative per visible
1451/// symbol so a diagnostic lists each contender once.
1452fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1453    let mut distinct: Vec<CodeUnit> = Vec::new();
1454    for unit in units {
1455        if !distinct
1456            .iter()
1457            .any(|existing| same_visible_symbol(existing, unit))
1458        {
1459            distinct.push(unit.clone());
1460        }
1461    }
1462    distinct
1463}
1464
1465impl<'a> VisibilityIndex<'a> {
1466    pub fn cpp(&self) -> &'a dyn CppSource {
1467        self.cpp
1468    }
1469
1470    /// The request-scope proof this index was built with (issue #2414 step 3).
1471    pub fn token(&self) -> QueryToken<'a> {
1472        self.token
1473    }
1474
1475    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1476    /// bypassing the include-closure walk [`Self::build`] performs.
1477    ///
1478    /// The resolver's own unit tests drive the type-resolution paths against a
1479    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1480    /// because they need a real `CppAnalyzer`, so the struct literal they used
1481    /// to write inline is here instead of thirty-three public fields.
1482    #[cfg(any(test, feature = "test-support"))]
1483    pub fn from_visible_files_for_test(
1484        cpp: &'a dyn CppSource,
1485        token: QueryToken<'a>,
1486        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1487    ) -> Self {
1488        let visible_source_files_by_root = visible_by_file
1489            .iter()
1490            .map(|(file, visible)| {
1491                (
1492                    file.clone(),
1493                    visible
1494                        .iter()
1495                        .map(|unit| unit.source().clone())
1496                        .chain(std::iter::once(file.clone()))
1497                        .collect(),
1498                )
1499            })
1500            .collect();
1501        let mut global_field_internal_linkage = HashMap::default();
1502        Self {
1503            cpp,
1504            token,
1505            visible_by_identifier: build_visible_identifier_index(
1506                &CppGraphSource::from_source(cpp, token),
1507                &visible_by_file,
1508                &visible_source_files_by_root,
1509                &mut global_field_internal_linkage,
1510            ),
1511            global_field_internal_linkage,
1512            visible_by_file,
1513            visible_source_files_by_root,
1514            alias_cells: Mutex::new(HashMap::default()),
1515            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1516            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1517            project_using_index: OnceLock::new(),
1518            callable_reference_specs: Mutex::new(HashMap::default()),
1519            include_activation_cells: Mutex::new(HashMap::default()),
1520            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1521            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1522            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1523            conditional_include_projection_state_count: AtomicUsize::new(0),
1524            conditional_include_target_state_count: AtomicUsize::new(0),
1525            include_activation_build_count: AtomicUsize::new(0),
1526            using_donor_activation_count: AtomicUsize::new(0),
1527            using_namespace_lookup_count: AtomicUsize::new(0),
1528            using_name_candidate_inspection_count: AtomicUsize::new(0),
1529            callable_reference_spec_build_count: AtomicUsize::new(0),
1530            alias_source_parse_counts: Mutex::new(HashMap::default()),
1531            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1532            parser_alias_fallback_calls: AtomicUsize::new(0),
1533            parser_alias_fallback_files: AtomicUsize::new(0),
1534            parser_alias_source_parses: AtomicUsize::new(0),
1535            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1536            field_type_facts: Mutex::new(HashMap::default()),
1537            structured_alias_targets: Mutex::new(HashMap::default()),
1538            callable_comparables: Mutex::new(HashMap::default()),
1539            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1540            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1541            precise_parent_cache: Mutex::new(HashMap::default()),
1542            macro_event_cells: Mutex::new(HashMap::default()),
1543            macro_include_protection_cells: Mutex::new(HashMap::default()),
1544            macro_environment_cursors: Mutex::new(HashMap::default()),
1545            macro_replacements: Mutex::new(HashMap::default()),
1546            macro_local_binding_templates: Mutex::new(HashMap::default()),
1547            macro_replacement_bodies: Mutex::new(HashMap::default()),
1548            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1549            macro_replacement_parse_count: AtomicUsize::new(0),
1550            macro_event_application_count: AtomicUsize::new(0),
1551            macro_environment_copy_count: AtomicUsize::new(0),
1552            macro_environment_request_count: AtomicUsize::new(0),
1553            cpp_template_metadata: HashMap::default(),
1554            cpp_template_families: HashMap::default(),
1555            qualified_candidate_inspections: AtomicUsize::new(0),
1556            target_preserving_type_resolution_count: AtomicUsize::new(0),
1557        }
1558    }
1559
1560    /// The index's own C++ source, in the dispatching-analyzer shape.
1561    ///
1562    /// Four resolution paths reach the workspace through the C++ analyzer they
1563    /// already hold rather than through the analyzer the query was issued
1564    /// against; before the move they passed `&CppAnalyzer` straight into a
1565    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
1566    fn cpp_source(&self) -> CppGraphSource<'a> {
1567        CppGraphSource::from_source(self.cpp, self.token)
1568    }
1569
1570    pub fn build(
1571        cpp: &'a dyn CppSource,
1572        token: QueryToken<'a>,
1573        analyzer: &CppGraphSource<'_>,
1574        roots: &HashSet<ProjectFile>,
1575    ) -> Self {
1576        Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1577    }
1578
1579    pub fn build_with_cancellation(
1580        cpp: &'a dyn CppSource,
1581        token: QueryToken<'a>,
1582        analyzer: &CppGraphSource<'_>,
1583        roots: &HashSet<ProjectFile>,
1584        cancellation: Option<&CancellationToken>,
1585    ) -> Self {
1586        let visibility_started = Instant::now();
1587        let include_targets = cpp.include_target_index();
1588        let includes_started = Instant::now();
1589        let mut include_graph = IncludeGraph::default();
1590        for root in roots {
1591            include_graph.extend_with(root, cancellation, &mut |file| {
1592                cpp_include_paths(&cpp.visibility_import_statements(token, file))
1593                    .into_iter()
1594                    .flat_map(|include| {
1595                        resolve_include_targets_with_index(file, &include, include_targets)
1596                    })
1597                    .collect()
1598            });
1599        }
1600        let include_elapsed = includes_started.elapsed();
1601        let include_file_count = include_graph.files().count();
1602        let visible_source_files_by_root = roots
1603            .iter()
1604            .map(|root| {
1605                (
1606                    root.clone(),
1607                    include_graph.reachable_files(root, cancellation),
1608                )
1609            })
1610            .collect::<HashMap<_, _>>();
1611        let mut visibility_stats = BoundedVisibilityStats::default();
1612        let mut visible_by_file = build_bounded_visible_declarations(
1613            cpp,
1614            token,
1615            analyzer,
1616            roots,
1617            &visible_source_files_by_root,
1618            cancellation,
1619            &mut visibility_stats,
1620        );
1621        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
1622            eprintln!(
1623                "BIFROST_CPP_VISIBILITY_STATS total_ms={} include_ms={} include_files={} rounds={} root_names={} identifier_lookups={} candidate_units={} candidate_sources={} declaration_reads={} declaration_units={} selected_units={} dependency_ast_nodes={} dependency_names={} lookup_ms={} declaration_ms={} dependency_ast_ms={}",
1624                visibility_started.elapsed().as_millis(),
1625                include_elapsed.as_millis(),
1626                include_file_count,
1627                visibility_stats.rounds,
1628                visibility_stats.root_names,
1629                visibility_stats.identifier_lookups,
1630                visibility_stats.candidate_units,
1631                visibility_stats.candidate_sources,
1632                visibility_stats.declaration_reads,
1633                visibility_stats.declaration_units,
1634                visibility_stats.selected_units,
1635                visibility_stats.dependency_ast_nodes,
1636                visibility_stats.dependency_names,
1637                visibility_stats.lookup_elapsed.as_millis(),
1638                visibility_stats.declaration_elapsed.as_millis(),
1639                visibility_stats.dependency_ast_elapsed.as_millis(),
1640            );
1641        }
1642        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
1643        let finalize_started = Instant::now();
1644        if report_stats {
1645            eprintln!(
1646                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
1647                visible_by_file.len(),
1648                visible_by_file.values().map(HashSet::len).sum::<usize>(),
1649            );
1650        }
1651        let owner_started = Instant::now();
1652        if report_stats {
1653            eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
1654        }
1655        let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
1656        if report_stats {
1657            eprintln!(
1658                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
1659                owner_stats.unseen_owners,
1660                owner_stats.definition_lookups,
1661                owner_stats.admitted,
1662                owner_started.elapsed().as_millis(),
1663            );
1664        }
1665        let mut global_field_internal_linkage = HashMap::default();
1666        let identifier_started = Instant::now();
1667        if report_stats {
1668            eprintln!(
1669                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
1670            );
1671        }
1672        let visible_by_identifier = build_visible_identifier_index(
1673            analyzer,
1674            &visible_by_file,
1675            &visible_source_files_by_root,
1676            &mut global_field_internal_linkage,
1677        );
1678        if report_stats {
1679            eprintln!(
1680                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
1681                visible_by_identifier.len(),
1682                visible_by_identifier
1683                    .values()
1684                    .map(HashMap::len)
1685                    .sum::<usize>(),
1686                visible_by_identifier
1687                    .values()
1688                    .flat_map(HashMap::values)
1689                    .map(Vec::len)
1690                    .sum::<usize>(),
1691                identifier_started.elapsed().as_millis(),
1692            );
1693        }
1694        let mut cpp_template_metadata = HashMap::default();
1695        let metadata_started = Instant::now();
1696        let mut template_classes = 0usize;
1697        if report_stats {
1698            eprintln!(
1699                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
1700            );
1701        }
1702        for unit in visible_by_file
1703            .values()
1704            .flatten()
1705            .filter(|unit| unit.is_class())
1706        {
1707            template_classes += 1;
1708            if cpp_template_metadata.contains_key(unit) {
1709                continue;
1710            }
1711            if let Some(metadata) = cpp.template_metadata(unit) {
1712                cpp_template_metadata.insert(unit.clone(), metadata);
1713            }
1714        }
1715        if report_stats {
1716            eprintln!(
1717                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
1718                template_classes,
1719                cpp_template_metadata.len(),
1720                metadata_started.elapsed().as_millis(),
1721            );
1722        }
1723        let families_started = Instant::now();
1724        if report_stats {
1725            eprintln!(
1726                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
1727            );
1728        }
1729        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1730        for (unit, metadata) in &cpp_template_metadata {
1731            cpp_template_families
1732                .entry(metadata.primary_fq_name.clone())
1733                .or_default()
1734                .push(unit.clone());
1735        }
1736        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
1737        // order above is a function of those hashes. Two mirrored headers can
1738        // declare one specialization; `select_template_specialization` treats
1739        // them as interchangeable and returns the family's first entry, so an
1740        // unsorted family made the reported declaration depend on the
1741        // workspace's absolute path and on unrelated files (#1836). Order the
1742        // family exactly as `build_visible_identifier_index` orders its
1743        // per-identifier candidate lists.
1744        for family in cpp_template_families.values_mut() {
1745            sort_lookup_units(family);
1746        }
1747        if report_stats {
1748            eprintln!(
1749                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
1750                cpp_template_families.len(),
1751                cpp_template_families.values().map(Vec::len).sum::<usize>(),
1752                families_started.elapsed().as_millis(),
1753            );
1754            eprintln!(
1755                "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
1756                visible_by_file.len(),
1757                visible_by_file.values().map(HashSet::len).sum::<usize>(),
1758                finalize_started.elapsed().as_millis(),
1759                visibility_started.elapsed().as_millis(),
1760            );
1761        }
1762        Self {
1763            cpp,
1764            token,
1765            visible_by_file,
1766            visible_by_identifier,
1767            global_field_internal_linkage,
1768            visible_source_files_by_root,
1769            alias_cells: Mutex::new(HashMap::default()),
1770            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1771            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1772            project_using_index: OnceLock::new(),
1773            callable_reference_specs: Mutex::new(HashMap::default()),
1774            include_activation_cells: Mutex::new(HashMap::default()),
1775            compile_proven_guard_cells: Mutex::new(HashMap::default()),
1776            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1777            #[cfg(any(test, feature = "test-support"))]
1778            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1779            #[cfg(any(test, feature = "test-support"))]
1780            conditional_include_projection_state_count: AtomicUsize::new(0),
1781            #[cfg(any(test, feature = "test-support"))]
1782            conditional_include_target_state_count: AtomicUsize::new(0),
1783            #[cfg(any(test, feature = "test-support"))]
1784            include_activation_build_count: AtomicUsize::new(0),
1785            #[cfg(any(test, feature = "test-support"))]
1786            using_donor_activation_count: AtomicUsize::new(0),
1787            #[cfg(any(test, feature = "test-support"))]
1788            using_namespace_lookup_count: AtomicUsize::new(0),
1789            #[cfg(any(test, feature = "test-support"))]
1790            using_name_candidate_inspection_count: AtomicUsize::new(0),
1791            #[cfg(any(test, feature = "test-support"))]
1792            callable_reference_spec_build_count: AtomicUsize::new(0),
1793            #[cfg(any(test, feature = "test-support"))]
1794            alias_source_parse_counts: Mutex::new(HashMap::default()),
1795            #[cfg(any(test, feature = "test-support"))]
1796            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1797            parser_alias_fallback_calls: AtomicUsize::new(0),
1798            parser_alias_fallback_files: AtomicUsize::new(0),
1799            parser_alias_source_parses: AtomicUsize::new(0),
1800            parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
1801            field_type_facts: Mutex::new(HashMap::default()),
1802            structured_alias_targets: Mutex::new(HashMap::default()),
1803            callable_comparables: Mutex::new(HashMap::default()),
1804            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1805            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1806            precise_parent_cache: Mutex::new(HashMap::default()),
1807            macro_event_cells: Mutex::new(HashMap::default()),
1808            macro_include_protection_cells: Mutex::new(HashMap::default()),
1809            macro_environment_cursors: Mutex::new(HashMap::default()),
1810            macro_replacements: Mutex::new(HashMap::default()),
1811            macro_local_binding_templates: Mutex::new(HashMap::default()),
1812            macro_replacement_bodies: Mutex::new(HashMap::default()),
1813            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1814            #[cfg(any(test, feature = "test-support"))]
1815            macro_replacement_parse_count: AtomicUsize::new(0),
1816            #[cfg(any(test, feature = "test-support"))]
1817            macro_event_application_count: AtomicUsize::new(0),
1818            #[cfg(any(test, feature = "test-support"))]
1819            macro_environment_copy_count: AtomicUsize::new(0),
1820            #[cfg(any(test, feature = "test-support"))]
1821            macro_environment_request_count: AtomicUsize::new(0),
1822            cpp_template_metadata,
1823            cpp_template_families,
1824            #[cfg(any(test, feature = "test-support"))]
1825            qualified_candidate_inspections: AtomicUsize::new(0),
1826            #[cfg(any(test, feature = "test-support"))]
1827            target_preserving_type_resolution_count: AtomicUsize::new(0),
1828        }
1829    }
1830
1831    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1832        if file == target.source() {
1833            return true;
1834        }
1835        if self.global_field_has_internal_linkage(target) {
1836            return self
1837                .visible_source_files_by_root
1838                .get(file)
1839                .is_some_and(|sources| sources.contains(target.source()));
1840        }
1841        self.visible_by_file
1842            .get(file)
1843            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
1844    }
1845
1846    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
1847        self.global_field_internal_linkage
1848            .get(unit)
1849            .copied()
1850            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
1851    }
1852
1853    pub fn call_arity_evidence(
1854        &self,
1855        file: &ProjectFile,
1856        call: Node<'_>,
1857        source: &str,
1858    ) -> CallArityEvidence {
1859        self.call_arity_evidence_at(file, call, source, call.start_byte())
1860    }
1861
1862    /// Argument-count evidence for a call whose macro environment is not the
1863    /// one at its own byte offset.
1864    ///
1865    /// A call recovered from a macro replacement lives in a sentinel parse of
1866    /// its own, so its node offsets say nothing about which macros are active.
1867    /// `environment_byte` names the position in `file` whose macro environment
1868    /// governs the call: the macro definition site for a replacement body.
1869    pub fn call_arity_evidence_at(
1870        &self,
1871        file: &ProjectFile,
1872        call: Node<'_>,
1873        source: &str,
1874        environment_byte: usize,
1875    ) -> CallArityEvidence {
1876        let Some(arguments) = call
1877            .child_by_field_name("arguments")
1878            .or_else(|| call.child_by_field_name("parameters"))
1879            .or_else(|| call.child_by_field_name("value"))
1880            .or_else(|| first_named_child_of_kind(call, "argument_list"))
1881            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
1882        else {
1883            return CallArityEvidence::Exact(0);
1884        };
1885        let recovered_c_keyword_arguments =
1886            recovered_c_keyword_argument_count(file, call, arguments, source);
1887        let arguments = argument_children(arguments).collect::<Vec<_>>();
1888        if arguments
1889            .iter()
1890            .all(|argument| !argument_shape_may_change_arity(*argument))
1891        {
1892            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
1893        }
1894        let environment = self.macro_environment(file, environment_byte);
1895        let mut stack = Vec::new();
1896        let mut total = recovered_c_keyword_arguments;
1897        for argument in arguments {
1898            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
1899                return CallArityEvidence::Unknown;
1900            }
1901            let CallArityEvidence::Exact(spread) =
1902                self.argument_arity_evidence(argument, source, &environment, &mut stack)
1903            else {
1904                return CallArityEvidence::Unknown;
1905            };
1906            total += spread;
1907        }
1908        CallArityEvidence::Exact(total)
1909    }
1910
1911    fn argument_arity_evidence(
1912        &self,
1913        argument: Node<'_>,
1914        source: &str,
1915        environment: &MacroEnvironment,
1916        stack: &mut Vec<(ProjectFile, usize)>,
1917    ) -> CallArityEvidence {
1918        let (name, invocation_arguments, function_like) = match argument.kind() {
1919            "identifier" => (node_text(argument, source), None, false),
1920            "call_expression" => {
1921                let Some(function) = argument.child_by_field_name("function") else {
1922                    return CallArityEvidence::Exact(1);
1923                };
1924                if function.kind() != "identifier" {
1925                    return CallArityEvidence::Exact(1);
1926                }
1927                let Some(arguments) = argument.child_by_field_name("arguments") else {
1928                    return CallArityEvidence::Exact(1);
1929                };
1930                (node_text(function, source), Some(arguments), true)
1931            }
1932            _ => return CallArityEvidence::Exact(1),
1933        };
1934        let Some(binding) = environment.binding(name) else {
1935            return if environment.unknown_names {
1936                CallArityEvidence::Unknown
1937            } else {
1938                CallArityEvidence::Exact(1)
1939            };
1940        };
1941        if !binding.is_exact() {
1942            return CallArityEvidence::Unknown;
1943        }
1944        match (&binding.definition, invocation_arguments, function_like) {
1945            (MacroDefinition::Object { replacement }, None, false) => self
1946                .replacement_arity_evidence(
1947                    replacement,
1948                    &[],
1949                    &[],
1950                    source,
1951                    environment,
1952                    stack,
1953                    binding,
1954                ),
1955            (
1956                MacroDefinition::Function {
1957                    parameters,
1958                    replacement,
1959                },
1960                Some(arguments),
1961                true,
1962            ) => {
1963                let actuals = argument_children(arguments).collect::<Vec<_>>();
1964                if actuals.len() != parameters.len() {
1965                    CallArityEvidence::Unknown
1966                } else {
1967                    self.replacement_arity_evidence(
1968                        replacement,
1969                        parameters,
1970                        &actuals,
1971                        source,
1972                        environment,
1973                        stack,
1974                        binding,
1975                    )
1976                }
1977            }
1978            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
1979            _ => CallArityEvidence::Unknown,
1980        }
1981    }
1982
1983    #[allow(clippy::too_many_arguments)]
1984    fn replacement_arity_evidence(
1985        &self,
1986        replacement: &str,
1987        parameters: &[String],
1988        actuals: &[Node<'_>],
1989        actual_source: &str,
1990        environment: &MacroEnvironment,
1991        stack: &mut Vec<(ProjectFile, usize)>,
1992        binding: &MacroBinding,
1993    ) -> CallArityEvidence {
1994        let identity = (binding.source.clone(), binding.declaration_byte);
1995        if stack.contains(&identity) || replacement.trim().is_empty() {
1996            return CallArityEvidence::Unknown;
1997        }
1998        stack.push(identity);
1999        let parsed = self.parsed_macro_replacement(binding, replacement);
2000        let evidence = (|| {
2001            let ParsedMacroReplacement::Parsed {
2002                source: sentinel,
2003                tree,
2004            } = parsed.as_ref()
2005            else {
2006                return None;
2007            };
2008            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2009            let arguments = call.child_by_field_name("arguments")?;
2010            let mut total = 0usize;
2011            for argument in argument_children(arguments) {
2012                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2013                    return None;
2014                }
2015                if argument.kind() == "identifier"
2016                    && let Some(parameter_index) = parameters
2017                        .iter()
2018                        .position(|parameter| parameter == node_text(argument, sentinel))
2019                {
2020                    if !macro_expansion_shape_is_safe(
2021                        actuals[parameter_index],
2022                        actual_source,
2023                        &[],
2024                        environment,
2025                    ) {
2026                        return None;
2027                    }
2028                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2029                        actuals[parameter_index],
2030                        actual_source,
2031                        environment,
2032                        stack,
2033                    ) else {
2034                        return None;
2035                    };
2036                    total += spread;
2037                    continue;
2038                }
2039                let CallArityEvidence::Exact(spread) =
2040                    self.argument_arity_evidence(argument, sentinel, environment, stack)
2041                else {
2042                    return None;
2043                };
2044                total += spread;
2045            }
2046            Some(CallArityEvidence::Exact(total))
2047        })()
2048        .unwrap_or(CallArityEvidence::Unknown);
2049        stack.pop();
2050        evidence
2051    }
2052
2053    fn parsed_macro_replacement(
2054        &self,
2055        binding: &MacroBinding,
2056        replacement: &str,
2057    ) -> Arc<ParsedMacroReplacement> {
2058        let key = (binding.source.clone(), binding.declaration_byte);
2059        let mut cache = self
2060            .macro_replacements
2061            .lock()
2062            .expect("C++ macro replacement cache poisoned");
2063        if let Some(parsed) = cache.get(&key) {
2064            return Arc::clone(parsed);
2065        }
2066        #[cfg(any(test, feature = "test-support"))]
2067        self.macro_replacement_parse_count
2068            .fetch_add(1, Ordering::Relaxed);
2069        let source =
2070            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2071        let mut parser = Parser::new();
2072        let parsed = parser
2073            .set_language(&tree_sitter_cpp::LANGUAGE.into())
2074            .ok()
2075            .and_then(|()| parser.parse(&source, None))
2076            .filter(|tree| !tree.root_node().has_error())
2077            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2078                ParsedMacroReplacement::Parsed { source, tree }
2079            });
2080        let parsed = Arc::new(parsed);
2081        cache.insert(key, Arc::clone(&parsed));
2082        parsed
2083    }
2084
2085    /// Recover a typed local declared by an active C function-like macro.
2086    ///
2087    /// This is intentionally narrower than macro expansion. The replacement
2088    /// must parse as one declaration, and the invocation must bind every
2089    /// formal parameter to one structured argument. That is sufficient for
2090    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
2091    /// can make the binding provisional without erasing its last known
2092    /// definition; an explicit conflicting definition still replaces it with
2093    /// Unsupported. Malformed and statement-producing macros also fail closed.
2094    pub fn function_macro_local_binding<'tree>(
2095        &self,
2096        file: &ProjectFile,
2097        statement: Node<'tree>,
2098        source: &str,
2099    ) -> Option<MacroLocalBinding<'tree>> {
2100        if !is_c_source_file(file) {
2101            return None;
2102        }
2103        if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2104            return Some(binding);
2105        }
2106        let call = match statement.kind() {
2107            "call_expression" => statement,
2108            "expression_statement" if statement.named_child_count() == 1 => {
2109                statement.named_child(0)?
2110            }
2111            _ => return None,
2112        };
2113        if call.kind() != "call_expression" {
2114            return None;
2115        }
2116        let function = call.child_by_field_name("function")?;
2117        if function.kind() != "identifier" {
2118            return None;
2119        }
2120        let arguments = call.child_by_field_name("arguments")?;
2121        let actuals = argument_children(arguments).collect::<Vec<_>>();
2122        let environment = self.macro_environment(file, call.start_byte());
2123        let function_name = node_text(function, source);
2124        let binding = environment.binding(function_name)?;
2125        let MacroDefinition::Function {
2126            parameters,
2127            replacement,
2128        } = &binding.definition
2129        else {
2130            return None;
2131        };
2132        if actuals.len() != parameters.len() {
2133            return None;
2134        }
2135        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2136        let (type_name, type_node) = match &template.declared_type {
2137            MacroLocalBindingTypeTemplate::Parameter(index) => {
2138                let actual = *actuals.get(*index)?;
2139                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2140                    return None;
2141                }
2142                (node_text(actual, source).trim().to_string(), Some(actual))
2143            }
2144            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2145        };
2146        if type_name.is_empty() {
2147            return None;
2148        }
2149        Some(MacroLocalBinding {
2150            name: template.name.clone(),
2151            type_name,
2152            type_node,
2153            pointer_depth: template.pointer_depth,
2154        })
2155    }
2156
2157    fn macro_local_binding_template(
2158        &self,
2159        binding: &MacroBinding,
2160        parameters: &[String],
2161        replacement: &str,
2162    ) -> Option<Arc<MacroLocalBindingTemplate>> {
2163        let key = (binding.source.clone(), binding.declaration_byte);
2164        if let Some(template) = self
2165            .macro_local_binding_templates
2166            .lock()
2167            .expect("C++ macro local-binding cache poisoned")
2168            .get(&key)
2169        {
2170            return template.clone();
2171        }
2172        let template = (|| {
2173            let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2174            let sentinel = body.source.as_str();
2175            let statements = body.statements()?;
2176            if statements.named_child_count() != 1 {
2177                return None;
2178            }
2179            let declaration = statements.named_child(0)?;
2180            if declaration.kind() != "declaration" {
2181                return None;
2182            }
2183            let type_node = declaration
2184                .child_by_field_name("type")
2185                .or_else(|| first_type_child(declaration))?;
2186            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
2187                let mut cursor = declaration.walk();
2188                declaration.named_children(&mut cursor).find_map(|child| {
2189                    if child.kind() == "init_declarator" {
2190                        child.child_by_field_name("declarator")
2191                    } else {
2192                        is_declarator_node(child).then_some(child)
2193                    }
2194                })
2195            })?;
2196            let name = extract_variable_name(declarator, sentinel)?;
2197            let pointer_depth = declared_name_indirection(declaration, type_node, &name, sentinel)?;
2198            let type_text = node_text(type_node, sentinel).trim();
2199            let declared_type = parameters
2200                .iter()
2201                .position(|parameter| parameter == type_text)
2202                .map(MacroLocalBindingTypeTemplate::Parameter)
2203                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
2204            Some(Arc::new(MacroLocalBindingTemplate {
2205                name,
2206                declared_type,
2207                pointer_depth,
2208            }))
2209        })();
2210        self.macro_local_binding_templates
2211            .lock()
2212            .expect("C++ macro local-binding cache poisoned")
2213            .insert(key, template.clone());
2214        template
2215    }
2216
2217    /// The parsed replacement body of the function-like macro `definition`
2218    /// defines, or `None` when the replacement cannot be recovered exactly.
2219    ///
2220    /// `definition` is the defining `preproc_function_def` node in `file`, so
2221    /// the result describes that definition rather than whichever same-named
2222    /// macro a later reference resolves to.
2223    pub fn function_macro_replacement_body(
2224        &self,
2225        file: &ProjectFile,
2226        definition: Node<'_>,
2227        source: &str,
2228    ) -> Option<Arc<ParsedReplacementBody>> {
2229        debug_assert_eq!(definition.kind(), "preproc_function_def");
2230        let MacroDefinition::Function {
2231            parameters,
2232            replacement,
2233        } = Self::decode_macro_definition(definition, source)
2234        else {
2235            return None;
2236        };
2237        self.parsed_macro_replacement_body(
2238            &(file.clone(), definition.start_byte()),
2239            &parameters,
2240            &replacement,
2241        )
2242    }
2243
2244    /// Parse one function-like macro replacement inside the shared sentinel.
2245    ///
2246    /// The parse fails closed, and the failure is cached, whenever the
2247    /// sentinel tree carries an error or the replacement uses preprocessor
2248    /// syntax that has no C++ meaning. Token pasting and stringizing produce
2249    /// `ERROR` nodes; `__VA_ARGS__` parses as an ordinary identifier and is
2250    /// therefore rejected from the parsed tree instead of the source text.
2251    fn parsed_macro_replacement_body(
2252        &self,
2253        key: &(ProjectFile, usize),
2254        parameters: &[String],
2255        replacement: &str,
2256    ) -> Option<Arc<ParsedReplacementBody>> {
2257        if let Some(body) = self
2258            .macro_replacement_bodies
2259            .lock()
2260            .expect("C++ macro replacement body cache poisoned")
2261            .get(key)
2262        {
2263            return body.clone();
2264        }
2265        let body = (|| {
2266            if replacement.trim().is_empty() {
2267                return None;
2268            }
2269            let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
2270            let mut parser = Parser::new();
2271            parser
2272                .set_language(&tree_sitter_cpp::LANGUAGE.into())
2273                .ok()?;
2274            let tree = parser.parse(&source, None)?;
2275            if tree.root_node().has_error() {
2276                return None;
2277            }
2278            let body = ParsedReplacementBody {
2279                source,
2280                tree,
2281                body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
2282                parameters: parameters.to_vec(),
2283            };
2284            body.statements()?;
2285            if body.expands_variadic_arguments() {
2286                return None;
2287            }
2288            Some(Arc::new(body))
2289        })();
2290        self.macro_replacement_bodies
2291            .lock()
2292            .expect("C++ macro replacement body cache poisoned")
2293            .insert(key.clone(), body.clone());
2294        body
2295    }
2296
2297    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
2298        let replacement = node
2299            .child_by_field_name("value")
2300            .map(|value| node_text(value, source).to_string())
2301            .unwrap_or_default();
2302        if node.kind() == "preproc_def" {
2303            return MacroDefinition::Object { replacement };
2304        }
2305        let Some(parameters) = node.child_by_field_name("parameters") else {
2306            return MacroDefinition::Unsupported;
2307        };
2308        if (0..parameters.child_count()).any(|index| {
2309            parameters
2310                .child(index)
2311                .is_some_and(|child| child.kind() == "...")
2312        }) {
2313            return MacroDefinition::Unsupported;
2314        }
2315        let parameters = (0..parameters.named_child_count())
2316            .filter_map(|index| parameters.named_child(index))
2317            .map(|parameter| node_text(parameter, source).to_string())
2318            .collect();
2319        MacroDefinition::Function {
2320            parameters,
2321            replacement,
2322        }
2323    }
2324
2325    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
2326        self.macro_event_cells
2327            .lock()
2328            .expect("C++ macro event cache poisoned")
2329            .entry(file.clone())
2330            .or_default()
2331            .clone()
2332    }
2333
2334    pub fn macro_environment_cursor_cell(&self, file: &ProjectFile) -> MacroEnvironmentCursorCell {
2335        let key = (file.clone(), std::thread::current().id());
2336        self.macro_environment_cursors
2337            .lock()
2338            .expect("C++ macro environment cursor cache poisoned")
2339            .entry(key)
2340            .or_default()
2341            .clone()
2342    }
2343
2344    pub fn macro_environment(
2345        &self,
2346        file: &ProjectFile,
2347        before_byte: usize,
2348    ) -> Arc<MacroEnvironment> {
2349        #[cfg(any(test, feature = "test-support"))]
2350        self.macro_environment_request_count
2351            .fetch_add(1, Ordering::Relaxed);
2352        let cell = self.macro_event_cell(file);
2353        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2354        let frontier = events.partition_point(|event| event.byte() < before_byte);
2355        let cursor_cell = self.macro_environment_cursor_cell(file);
2356        let mut cursor = cursor_cell
2357            .lock()
2358            .expect("C++ macro environment cursor poisoned");
2359        if frontier < cursor.frontier {
2360            *cursor = MacroEnvironmentCursor::default();
2361        }
2362        // Seed the TU's build-proven defines once, before any event applies
2363        // (#2011). They are facts of the whole compile, so they hold from the
2364        // first byte; a later explicit #undef event still overrides them
2365        // through `known_undefined_names`.
2366        if cursor.frontier == 0 {
2367            let proven = self.compile_proven_guards(file);
2368            if !proven.is_empty() && cursor.environment.build_proven_defines.len() != proven.len() {
2369                Arc::make_mut(&mut cursor.environment).build_proven_defines = proven
2370                    .iter()
2371                    .filter_map(|guard| match guard {
2372                        PreprocessorGuard::Defined(name) => Some(name.clone()),
2373                        _ => None,
2374                    })
2375                    .collect();
2376            }
2377        }
2378        if frontier > cursor.frontier {
2379            #[cfg(any(test, feature = "test-support"))]
2380            if Arc::strong_count(&cursor.environment) > 1 {
2381                self.macro_environment_copy_count
2382                    .fetch_add(1, Ordering::Relaxed);
2383            }
2384            let start = cursor.frontier;
2385            let environment = Arc::make_mut(&mut cursor.environment);
2386            let mut include_stack = HashSet::from_iter([file.clone()]);
2387            for event in &events[start..frontier] {
2388                self.apply_macro_event(file, event, environment, &mut include_stack);
2389            }
2390            cursor.frontier = frontier;
2391        }
2392        Arc::clone(&cursor.environment)
2393    }
2394
2395    /// Whether `name` is bound as a macro at `before_byte` in `file`,
2396    /// including a binding this environment cannot pin to one replacement
2397    /// (a conditional `#define`, or a function-like macro).
2398    ///
2399    /// [`Self::object_macro_replacement_at`] collapses every such binding to
2400    /// `None`, which is indistinguishable from "not a macro at all". A caller
2401    /// that must not read a macro token as an ordinary type name needs the two
2402    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
2403    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
2404        self.macro_environment(file, before_byte)
2405            .binding(name)
2406            .is_some()
2407    }
2408
2409    pub fn macro_name_may_be_bound_at(
2410        &self,
2411        file: &ProjectFile,
2412        name: &str,
2413        before_byte: usize,
2414    ) -> bool {
2415        self.macro_environment(file, before_byte).may_bind(name)
2416    }
2417
2418    /// Whether the active macro binding at this reference is the requested
2419    /// indexed definition. Name equality alone is not enough because two
2420    /// headers can define the same macro for different translation units.
2421    pub fn macro_binding_matches_target_at(
2422        &self,
2423        analyzer: &CppGraphSource<'_>,
2424        file: &ProjectFile,
2425        name: &str,
2426        before_byte: usize,
2427        target: &CodeUnit,
2428    ) -> bool {
2429        let environment = self.macro_environment(file, before_byte);
2430        let Some(binding) = environment.binding(name) else {
2431            return false;
2432        };
2433        if binding.definition == MacroDefinition::Unsupported {
2434            return false;
2435        }
2436        // A normal header guard makes the replacement text conditional, but
2437        // it does not erase the definition site's source and byte identity.
2438        // Keep that identity even when expansion details are not exact.
2439        if binding.source != *target.source() {
2440            return false;
2441        }
2442        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
2443            return false;
2444        };
2445        analyzer.ranges(target).iter().any(|range| {
2446            let Some(mut node) = node_for_exact_range(prepared.tree().root_node(), range) else {
2447                return false;
2448            };
2449            while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
2450                let Some(parent) = node.parent() else {
2451                    return false;
2452                };
2453                node = parent;
2454            }
2455            node.start_byte() == binding.declaration_byte
2456        })
2457    }
2458
2459    /// Resolve an ordinary expression-position macro token at its exact byte.
2460    ///
2461    /// Calls and preprocessor-condition tokens have separate resolution
2462    /// surfaces. Declaration names, macro parameters, and labels are not
2463    /// references. Keeping that role policy here makes forward and both
2464    /// inverse graph builders consume the same activation verdict (#2093).
2465    pub fn resolve_ordinary_macro_reference(
2466        &self,
2467        analyzer: &CppGraphSource<'_>,
2468        file: &ProjectFile,
2469        node: Node<'_>,
2470        source: &str,
2471    ) -> OrdinaryMacroReferenceResolution {
2472        if !is_ordinary_macro_reference_node(node) {
2473            return OrdinaryMacroReferenceResolution::Missing;
2474        }
2475        let name = node_text(node, source);
2476        if name.is_empty() {
2477            return OrdinaryMacroReferenceResolution::Missing;
2478        }
2479        let visible = self
2480            .visible_identifier_candidates(file, name)
2481            .filter(|candidate| candidate.is_macro())
2482            .cloned()
2483            .collect::<Vec<_>>();
2484        let mut exact = Vec::new();
2485        for candidate in &visible {
2486            if self.macro_binding_matches_target_at(
2487                analyzer,
2488                file,
2489                name,
2490                node.start_byte(),
2491                candidate,
2492            ) && !exact
2493                .iter()
2494                .any(|existing| same_visible_symbol(existing, candidate))
2495            {
2496                exact.push(candidate.clone());
2497            }
2498        }
2499        match exact.len() {
2500            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
2501            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
2502            0 if !visible.is_empty()
2503                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
2504            {
2505                OrdinaryMacroReferenceResolution::Ambiguous
2506            }
2507            0 => OrdinaryMacroReferenceResolution::Missing,
2508        }
2509    }
2510
2511    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
2512    ///
2513    /// The ordinary census deliberately skips every `ERROR` subtree. This
2514    /// separate, precision-only frontier admits only roles that retain enough
2515    /// structure for the C usage graph to interpret independently (#2089).
2516    /// Macro evidence comes from this visibility index at the exact byte; no
2517    /// source-text parsing or terminal-name fallback is used.
2518    pub fn recovered_c_reference_ranges(
2519        &self,
2520        file: &ProjectFile,
2521        root: Node<'_>,
2522        source: &str,
2523        limit: usize,
2524    ) -> RecoveredCReferenceRanges {
2525        if !is_c_source_file(file) {
2526            return RecoveredCReferenceRanges::Complete(Vec::new());
2527        }
2528        let mut ranges = Vec::new();
2529        let mut seen = HashSet::default();
2530        let mut stack = vec![(root, root.is_error())];
2531        while let Some((node, inside_error)) = stack.pop() {
2532            let inside_error = inside_error || node.is_error();
2533            if inside_error
2534                && recovered_c_reference_node(self, file, node, source)
2535                && seen.insert((node.start_byte(), node.end_byte()))
2536            {
2537                if ranges.len() == limit {
2538                    return RecoveredCReferenceRanges::LimitExceeded;
2539                }
2540                ranges.push(Range {
2541                    start_byte: node.start_byte(),
2542                    end_byte: node.end_byte(),
2543                    start_line: node.start_position().row,
2544                    end_line: node.end_position().row,
2545                });
2546            }
2547            let mut cursor = node.walk();
2548            for child in node.named_children(&mut cursor) {
2549                stack.push((child, inside_error));
2550            }
2551        }
2552        ranges.sort_unstable();
2553        RecoveredCReferenceRanges::Complete(ranges)
2554    }
2555
2556    /// Whether this target is an indexed macro visible from this file.
2557    ///
2558    /// An unresolved conditional can make more than one same-name macro a
2559    /// possible active binding. Each possible target can keep the site as an
2560    /// unproven hit. A macro in an unrelated translation unit stays excluded.
2561    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2562        self.visible_identifier_candidates(file, target.identifier())
2563            .filter(|candidate| candidate.is_macro())
2564            .any(|candidate| {
2565                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
2566            })
2567    }
2568
2569    pub fn object_macro_replacement_at(
2570        &self,
2571        file: &ProjectFile,
2572        name: &str,
2573        before_byte: usize,
2574    ) -> Option<String> {
2575        let environment = self.macro_environment(file, before_byte);
2576        let binding = environment.binding(name)?;
2577        if !binding.exact {
2578            return None;
2579        }
2580        match &binding.definition {
2581            MacroDefinition::Object { replacement } => Some(replacement.clone()),
2582            MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
2583        }
2584    }
2585
2586    fn apply_macro_events(
2587        &self,
2588        file: &ProjectFile,
2589        before_byte: Option<usize>,
2590        environment: &mut MacroEnvironment,
2591        include_stack: &mut HashSet<ProjectFile>,
2592    ) {
2593        if !include_stack.insert(file.clone()) {
2594            return;
2595        }
2596        if self.cpp.prepared_syntax(self.token, file).is_none() {
2597            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
2598            include_stack.remove(file);
2599            return;
2600        }
2601        match self.macro_include_protection(file) {
2602            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
2603                Some(binding) if binding.is_exact() => {
2604                    include_stack.remove(file);
2605                    return;
2606                }
2607                Some(_) | None if environment.unknown_names => {
2608                    let mut ambiguous_seen = HashSet::default();
2609                    self.mark_macro_events_ambiguous(
2610                        file,
2611                        environment,
2612                        &mut ambiguous_seen,
2613                        file,
2614                        before_byte.unwrap_or_default(),
2615                    );
2616                    include_stack.remove(file);
2617                    return;
2618                }
2619                Some(_) => {
2620                    let mut ambiguous_seen = HashSet::default();
2621                    self.mark_macro_events_ambiguous(
2622                        file,
2623                        environment,
2624                        &mut ambiguous_seen,
2625                        file,
2626                        before_byte.unwrap_or_default(),
2627                    );
2628                    include_stack.remove(file);
2629                    return;
2630                }
2631                None => {}
2632            },
2633            MacroIncludeProtection::PragmaOnce => {
2634                if !environment.applied_pragma_once_files.insert(file.clone()) {
2635                    include_stack.remove(file);
2636                    return;
2637                }
2638                if environment.maybe_applied_pragma_once_files.remove(file) {
2639                    // A prior conditional include may already have consumed the pragma-once
2640                    // header. This unconditional include guarantees it is consumed now, but
2641                    // cannot prove whether its events occur before or after intervening local
2642                    // macro changes, so preserve the union as ambiguous.
2643                    let mut ambiguous_seen = HashSet::default();
2644                    environment.applied_pragma_once_files.remove(file);
2645                    self.mark_macro_events_ambiguous(
2646                        file,
2647                        environment,
2648                        &mut ambiguous_seen,
2649                        file,
2650                        before_byte.unwrap_or_default(),
2651                    );
2652                    environment.maybe_applied_pragma_once_files.remove(file);
2653                    environment.applied_pragma_once_files.insert(file.clone());
2654                    include_stack.remove(file);
2655                    return;
2656                }
2657            }
2658            MacroIncludeProtection::None => {}
2659        }
2660        let cell = self.macro_event_cell(file);
2661        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2662        for event in events {
2663            if before_byte.is_some_and(|limit| event.byte() >= limit) {
2664                break;
2665            }
2666            self.apply_macro_event(file, event, environment, include_stack);
2667        }
2668        include_stack.remove(file);
2669    }
2670
2671    fn apply_macro_event(
2672        &self,
2673        file: &ProjectFile,
2674        event: &MacroEvent,
2675        environment: &mut MacroEnvironment,
2676        include_stack: &mut HashSet<ProjectFile>,
2677    ) {
2678        #[cfg(any(test, feature = "test-support"))]
2679        self.macro_event_application_count
2680            .fetch_add(1, Ordering::Relaxed);
2681        match event {
2682            MacroEvent::Define {
2683                name,
2684                binding,
2685                conditional,
2686                byte,
2687            } => {
2688                match conditional
2689                    .then(|| self.macro_event_condition_value(file, *byte, environment))
2690                    .unwrap_or(Some(true))
2691                {
2692                    Some(true) => environment.insert(name.clone(), binding.clone()),
2693                    Some(false) => {}
2694                    None => Self::merge_conditional_macro_definition(
2695                        environment,
2696                        name,
2697                        binding,
2698                        file,
2699                        *byte,
2700                    ),
2701                }
2702            }
2703            MacroEvent::Undef {
2704                name,
2705                conditional,
2706                byte,
2707            } => {
2708                match conditional
2709                    .then(|| self.macro_event_condition_value(file, *byte, environment))
2710                    .unwrap_or(Some(true))
2711                {
2712                    Some(true) => environment.remove(name),
2713                    Some(false) => {}
2714                    None => {
2715                        if environment.binding(name).is_some() {
2716                            environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
2717                        }
2718                    }
2719                }
2720            }
2721            MacroEvent::Include {
2722                targets,
2723                conditional,
2724                byte,
2725            } => {
2726                let condition = conditional
2727                    .then(|| self.macro_event_condition_value(file, *byte, environment))
2728                    .unwrap_or(Some(true));
2729                if condition == Some(false) {
2730                    return;
2731                }
2732                if targets.is_empty() {
2733                    environment.mark_unknown_names(file, *byte);
2734                    return;
2735                }
2736                if condition.is_none() || targets.len() > 1 {
2737                    let mut ambiguous_seen = HashSet::default();
2738                    for target in targets {
2739                        self.mark_macro_events_ambiguous(
2740                            target,
2741                            environment,
2742                            &mut ambiguous_seen,
2743                            file,
2744                            *byte,
2745                        );
2746                    }
2747                } else if let Some(target) = targets.first() {
2748                    self.apply_macro_events(target, None, environment, include_stack);
2749                }
2750            }
2751            MacroEvent::Invalidate { byte } => {
2752                for binding in environment.bindings.values_mut() {
2753                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
2754                }
2755            }
2756        }
2757    }
2758
2759    /// Evaluate the structured conditional path that owns one macro event.
2760    ///
2761    /// `Some(true)` and `Some(false)` are proofs from exact macro bindings at
2762    /// this source byte. `None` preserves the old conditional merge when a
2763    /// build/configuration input or an unsupported expression is involved.
2764    fn macro_event_condition_value(
2765        &self,
2766        file: &ProjectFile,
2767        event_byte: usize,
2768        environment: &MacroEnvironment,
2769    ) -> Option<bool> {
2770        let prepared = self.cpp.prepared_syntax(self.token, file)?;
2771        let source = prepared.source();
2772        let root = prepared.tree().root_node();
2773        let descendant = root.descendant_for_byte_range(
2774            event_byte,
2775            event_byte.saturating_add(1).min(source.len()),
2776        )?;
2777        let mut unknown = false;
2778        let mut current = descendant.parent();
2779        while let Some(conditional) = current {
2780            if matches!(
2781                conditional.kind(),
2782                "preproc_if" | "preproc_ifdef" | "preproc_elif"
2783            ) && !is_file_covering_include_guard(conditional, source)
2784                && preprocessor_conditional_contains_descendant(conditional, descendant)
2785            {
2786                let mut value = match conditional.kind() {
2787                    "preproc_ifdef" => {
2788                        let name = conditional.child_by_field_name("name")?;
2789                        let defined =
2790                            self.macro_name_defined_value(environment, node_text(name, source));
2791                        match conditional.child(0)?.kind() {
2792                            "#ifdef" => defined,
2793                            "#ifndef" => defined.map(|defined| !defined),
2794                            _ => None,
2795                        }
2796                    }
2797                    "preproc_if" | "preproc_elif" => conditional
2798                        .child_by_field_name("condition")
2799                        .and_then(|condition| {
2800                            self.preprocessor_integer_value(
2801                                condition,
2802                                source,
2803                                environment,
2804                                &mut Vec::new(),
2805                                0,
2806                            )
2807                        })
2808                        .map(|value| value != 0),
2809                    _ => unreachable!(),
2810                };
2811                if conditional
2812                    .child_by_field_name("alternative")
2813                    .is_some_and(|alternative| {
2814                        alternative.start_byte() <= descendant.start_byte()
2815                            && descendant.end_byte() <= alternative.end_byte()
2816                    })
2817                {
2818                    value = value.map(|value| !value);
2819                }
2820                match value {
2821                    Some(true) => {}
2822                    Some(false) => return Some(false),
2823                    None => unknown = true,
2824                }
2825            }
2826            current = conditional.parent();
2827        }
2828        (!unknown).then_some(true)
2829    }
2830
2831    fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
2832        if environment.known_undefined_names.contains(name) {
2833            return Some(false);
2834        }
2835        if let Some(binding) = environment.binding(name) {
2836            return binding.is_exact().then_some(true);
2837        }
2838        environment
2839            .build_proven_defines
2840            .contains(name)
2841            .then_some(true)
2842    }
2843
2844    fn preprocessor_integer_value(
2845        &self,
2846        expression: Node<'_>,
2847        source: &str,
2848        environment: &MacroEnvironment,
2849        expansion_stack: &mut Vec<(ProjectFile, usize)>,
2850        depth: usize,
2851    ) -> Option<i128> {
2852        // Macro replacement graphs can cycle. This explicit bound makes the
2853        // otherwise recursive AST evaluation stack-safe for hostile input.
2854        if depth >= 64 {
2855            return None;
2856        }
2857        match expression.kind() {
2858            "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
2859            "identifier" | "type_identifier" => {
2860                let binding = environment.binding(node_text(expression, source))?;
2861                if !binding.is_exact() {
2862                    return None;
2863                }
2864                let MacroDefinition::Object { replacement } = &binding.definition else {
2865                    return None;
2866                };
2867                let identity = (binding.source.clone(), binding.declaration_byte);
2868                if expansion_stack.contains(&identity) {
2869                    return None;
2870                }
2871                expansion_stack.push(identity);
2872                let parsed = self.parsed_macro_replacement(binding, replacement);
2873                let value = match parsed.as_ref() {
2874                    ParsedMacroReplacement::Parsed {
2875                        source: replacement_source,
2876                        tree,
2877                    } => first_descendant_of_kind(tree.root_node(), "call_expression")
2878                        .and_then(|call| call.child_by_field_name("arguments"))
2879                        .and_then(|arguments| argument_children(arguments).next())
2880                        .and_then(|argument| {
2881                            self.preprocessor_integer_value(
2882                                argument,
2883                                replacement_source,
2884                                environment,
2885                                expansion_stack,
2886                                depth + 1,
2887                            )
2888                        }),
2889                    ParsedMacroReplacement::Unsupported => None,
2890                };
2891                expansion_stack.pop();
2892                value
2893            }
2894            "preproc_defined" => {
2895                let mut cursor = expression.walk();
2896                let name = expression
2897                    .named_children(&mut cursor)
2898                    .find(|child| child.kind() == "identifier")?;
2899                self.macro_name_defined_value(environment, node_text(name, source))
2900                    .map(i128::from)
2901            }
2902            "parenthesized_expression" => expression.named_child(0).and_then(|child| {
2903                self.preprocessor_integer_value(
2904                    child,
2905                    source,
2906                    environment,
2907                    expansion_stack,
2908                    depth + 1,
2909                )
2910            }),
2911            "unary_expression" => {
2912                let operator = expression.child_by_field_name("operator")?.kind();
2913                let argument = expression.child_by_field_name("argument")?;
2914                let value = self.preprocessor_integer_value(
2915                    argument,
2916                    source,
2917                    environment,
2918                    expansion_stack,
2919                    depth + 1,
2920                )?;
2921                match operator {
2922                    "+" => Some(value),
2923                    "-" => value.checked_neg(),
2924                    "!" => Some(i128::from(value == 0)),
2925                    "~" => Some(!value),
2926                    _ => None,
2927                }
2928            }
2929            "binary_expression" => {
2930                let left = self.preprocessor_integer_value(
2931                    expression.child_by_field_name("left")?,
2932                    source,
2933                    environment,
2934                    expansion_stack,
2935                    depth + 1,
2936                )?;
2937                let right = self.preprocessor_integer_value(
2938                    expression.child_by_field_name("right")?,
2939                    source,
2940                    environment,
2941                    expansion_stack,
2942                    depth + 1,
2943                )?;
2944                match expression.child_by_field_name("operator")?.kind() {
2945                    "+" => left.checked_add(right),
2946                    "-" => left.checked_sub(right),
2947                    "*" => left.checked_mul(right),
2948                    "/" => left.checked_div(right),
2949                    "%" => left.checked_rem(right),
2950                    "<<" => u32::try_from(right)
2951                        .ok()
2952                        .and_then(|shift| left.checked_shl(shift)),
2953                    ">>" => u32::try_from(right)
2954                        .ok()
2955                        .and_then(|shift| left.checked_shr(shift)),
2956                    "<" => Some(i128::from(left < right)),
2957                    "<=" => Some(i128::from(left <= right)),
2958                    ">" => Some(i128::from(left > right)),
2959                    ">=" => Some(i128::from(left >= right)),
2960                    "==" => Some(i128::from(left == right)),
2961                    "!=" => Some(i128::from(left != right)),
2962                    "&" => Some(left & right),
2963                    "|" => Some(left | right),
2964                    "^" => Some(left ^ right),
2965                    "&&" => Some(i128::from(left != 0 && right != 0)),
2966                    "||" => Some(i128::from(left != 0 || right != 0)),
2967                    _ => None,
2968                }
2969            }
2970            _ => None,
2971        }
2972    }
2973
2974    fn mark_macro_events_ambiguous(
2975        &self,
2976        file: &ProjectFile,
2977        environment: &mut MacroEnvironment,
2978        include_stack: &mut HashSet<ProjectFile>,
2979        conditional_file: &ProjectFile,
2980        conditional_byte: usize,
2981    ) {
2982        if !include_stack.insert(file.clone()) {
2983            return;
2984        }
2985        if self.cpp.prepared_syntax(self.token, file).is_none() {
2986            environment.mark_unknown_names(conditional_file, conditional_byte);
2987            return;
2988        }
2989        match self.macro_include_protection(file) {
2990            MacroIncludeProtection::MacroGuard(guard) => {
2991                if environment
2992                    .binding(&guard)
2993                    .is_some_and(MacroBinding::is_exact)
2994                {
2995                    return;
2996                }
2997            }
2998            MacroIncludeProtection::PragmaOnce => {
2999                if environment.applied_pragma_once_files.contains(file) {
3000                    return;
3001                }
3002                environment
3003                    .maybe_applied_pragma_once_files
3004                    .insert(file.clone());
3005            }
3006            MacroIncludeProtection::None => {}
3007        }
3008        let cell = self.macro_event_cell(file);
3009        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3010        for event in events {
3011            #[cfg(any(test, feature = "test-support"))]
3012            self.macro_event_application_count
3013                .fetch_add(1, Ordering::Relaxed);
3014            match event {
3015                MacroEvent::Define { name, binding, .. } => {
3016                    Self::merge_conditional_macro_definition(
3017                        environment,
3018                        name,
3019                        binding,
3020                        conditional_file,
3021                        conditional_byte,
3022                    );
3023                }
3024                MacroEvent::Undef { name, .. } => {
3025                    if environment.binding(name).is_some() {
3026                        environment.insert(
3027                            name.clone(),
3028                            MacroBinding::ambiguous(conditional_file, conditional_byte),
3029                        );
3030                    } else {
3031                        environment.remove_known_undefined(name);
3032                    }
3033                }
3034                MacroEvent::Include { targets, .. } => {
3035                    if targets.is_empty() {
3036                        environment.mark_unknown_names(conditional_file, conditional_byte);
3037                        continue;
3038                    }
3039                    for target in targets {
3040                        self.mark_macro_events_ambiguous(
3041                            target,
3042                            environment,
3043                            include_stack,
3044                            conditional_file,
3045                            conditional_byte,
3046                        );
3047                    }
3048                }
3049                MacroEvent::Invalidate { .. } => {
3050                    for binding in environment.bindings.values_mut() {
3051                        *binding = MacroBinding::uncertain_from(
3052                            binding,
3053                            conditional_file,
3054                            conditional_byte,
3055                        );
3056                    }
3057                }
3058            }
3059        }
3060    }
3061
3062    fn merge_conditional_macro_definition(
3063        environment: &mut MacroEnvironment,
3064        name: &str,
3065        possible_binding: &MacroBinding,
3066        conditional_file: &ProjectFile,
3067        conditional_byte: usize,
3068    ) {
3069        // A conditional include can revisit an already-active guarded header.
3070        // If the possible branch defines the exact same macro, both outcomes
3071        // leave the binding unchanged; degrading it to Unknown would discard
3072        // proof because of an unrelated unresolved macro name (#2092).
3073        if environment.binding(name).is_some_and(|current| {
3074            current.definition != MacroDefinition::Unsupported
3075                && current.definition == possible_binding.definition
3076        }) {
3077            return;
3078        }
3079        environment.insert(
3080            name.to_string(),
3081            MacroBinding::ambiguous(conditional_file, conditional_byte),
3082        );
3083    }
3084
3085    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
3086        let cell = self
3087            .macro_include_protection_cells
3088            .lock()
3089            .expect("C++ include protection cache poisoned")
3090            .entry(file.clone())
3091            .or_default()
3092            .clone();
3093        cell.get_or_init(|| {
3094            self.cpp.prepared_syntax(self.token, file).map_or(
3095                MacroIncludeProtection::None,
3096                |prepared| {
3097                    top_level_macro_include_protection(
3098                        prepared.tree().root_node(),
3099                        prepared.source(),
3100                    )
3101                },
3102            )
3103        })
3104        .clone()
3105    }
3106
3107    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
3108        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3109            return Vec::new();
3110        };
3111        let source = prepared.source();
3112        let mut events = Vec::new();
3113        let mut stack = vec![prepared.tree().root_node()];
3114        while let Some(node) = stack.pop() {
3115            let conditional = has_preprocessor_conditional_ancestor(node, source);
3116            match node.kind() {
3117                "preproc_def" | "preproc_function_def" => {
3118                    let Some(name) = node.child_by_field_name("name") else {
3119                        continue;
3120                    };
3121                    let name = node_text(name, source).to_string();
3122                    events.push(MacroEvent::Define {
3123                        name,
3124                        binding: MacroBinding {
3125                            source: file.clone(),
3126                            declaration_byte: node.start_byte(),
3127                            definition: Self::decode_macro_definition(node, source),
3128                            exact: true,
3129                        },
3130                        byte: node.start_byte(),
3131                        conditional,
3132                    });
3133                    continue;
3134                }
3135                "preproc_include" => {
3136                    let Some(path) = node.child_by_field_name("path") else {
3137                        events.push(MacroEvent::Include {
3138                            targets: Vec::new(),
3139                            byte: node.start_byte(),
3140                            conditional,
3141                        });
3142                        continue;
3143                    };
3144                    let targets =
3145                        structured_include_path(path, source).map_or_else(Vec::new, |path| {
3146                            resolve_include_targets_with_index(
3147                                file,
3148                                path,
3149                                self.cpp.include_target_index(),
3150                            )
3151                        });
3152                    // An unresolved angle-bracket include crosses into an external system
3153                    // boundary that is absent from the source index. It must not poison all
3154                    // later local macro evidence. Quoted/project-local and computed includes,
3155                    // by contrast, may hide indexed macro state and therefore fail closed.
3156                    if targets.is_empty() && path.kind() == "system_lib_string" {
3157                        continue;
3158                    }
3159                    events.push(MacroEvent::Include {
3160                        targets,
3161                        byte: node.start_byte(),
3162                        conditional,
3163                    });
3164                    continue;
3165                }
3166                "preproc_call" => {
3167                    let Some(directive) = node.child_by_field_name("directive") else {
3168                        continue;
3169                    };
3170                    if node_text(directive, source) != "#undef" {
3171                        continue;
3172                    }
3173                    let name = node
3174                        .child_by_field_name("argument")
3175                        .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
3176                    if let Some(name) = name {
3177                        events.push(MacroEvent::Undef {
3178                            name,
3179                            byte: node.start_byte(),
3180                            conditional,
3181                        });
3182                    } else {
3183                        events.push(MacroEvent::Invalidate {
3184                            byte: node.start_byte(),
3185                        });
3186                    }
3187                    continue;
3188                }
3189                _ => {}
3190            }
3191            for index in (0..node.named_child_count()).rev() {
3192                if let Some(child) = node.named_child(index) {
3193                    stack.push(child);
3194                }
3195            }
3196        }
3197        events.sort_by_key(MacroEvent::byte);
3198        events
3199    }
3200
3201    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
3202        self.ordinary_type_import_cells
3203            .lock()
3204            .expect("C++ ordinary type import cache poisoned")
3205            .entry(file.clone())
3206            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
3207            .clone()
3208    }
3209
3210    pub fn project_using_index(
3211        &self,
3212        build: impl FnOnce() -> ProjectUsingIndex,
3213    ) -> &ProjectUsingIndex {
3214        self.project_using_index.get_or_init(build)
3215    }
3216
3217    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
3218        let mut files = self
3219            .visible_source_files_by_root
3220            .values()
3221            .flatten()
3222            .cloned()
3223            .collect::<HashSet<_>>()
3224            .into_iter()
3225            .collect::<Vec<_>>();
3226        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
3227        files
3228    }
3229
3230    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
3231        self.visible_source_files_by_root
3232            .get(root)
3233            .is_some_and(|files| files.contains(source))
3234    }
3235
3236    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
3237        let cached = self
3238            .visible_parser_alias_name_sets
3239            .read()
3240            .expect("visible parser alias-name cache poisoned")
3241            .get(file)
3242            .cloned();
3243        let cell = if let Some(cached) = cached {
3244            cached
3245        } else {
3246            let mut cells = self
3247                .visible_parser_alias_name_sets
3248                .write()
3249                .expect("visible parser alias-name cache poisoned");
3250            Arc::clone(
3251                cells
3252                    .entry(file.clone())
3253                    .or_insert_with(|| Arc::new(OnceLock::new())),
3254            )
3255        };
3256        cell.get_or_init(|| {
3257            #[cfg(any(test, feature = "test-support"))]
3258            self.visible_parser_alias_name_set_build_count
3259                .fetch_add(1, Ordering::Relaxed);
3260            let mut names = HashSet::default();
3261            let visible_files = self
3262                .visible_source_files_by_root
3263                .get(file)
3264                .cloned()
3265                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
3266            for visible_file in visible_files {
3267                let aliases = {
3268                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
3269                    Arc::clone(
3270                        cells
3271                            .entry(visible_file.clone())
3272                            .or_insert_with(|| Arc::new(OnceLock::new())),
3273                    )
3274                };
3275                for alias in aliases
3276                    .get_or_init(|| {
3277                        self.parser_alias_source_parses
3278                            .fetch_add(1, Ordering::Relaxed);
3279                        #[cfg(any(test, feature = "test-support"))]
3280                        {
3281                            *self
3282                                .alias_source_parse_counts
3283                                .lock()
3284                                .expect("alias source parse count lock")
3285                                .entry(visible_file.clone())
3286                                .or_default() += 1;
3287                        }
3288                        aliases_from_prepared_source(self.cpp, self.token, &visible_file)
3289                            .into_boxed_slice()
3290                    })
3291                    .iter()
3292                {
3293                    names.insert(alias.name.clone());
3294                }
3295            }
3296            names
3297        })
3298        .contains(name)
3299    }
3300
3301    pub fn parser_alias_name_may_resolve_to_target(
3302        &self,
3303        file: &ProjectFile,
3304        alias_name: &str,
3305        target: &CodeUnit,
3306    ) -> bool {
3307        let started = std::time::Instant::now();
3308        self.parser_alias_fallback_calls
3309            .fetch_add(1, Ordering::Relaxed);
3310        let mut files = 0usize;
3311        let matched = match self.visible_source_files_by_root.get(file) {
3312            None => {
3313                files = 1;
3314                self.file_alias_matches(self.cpp, file, alias_name, target)
3315            }
3316            Some(visible_files) => visible_files.iter().any(|visible_file| {
3317                files += 1;
3318                self.file_alias_matches(self.cpp, visible_file, alias_name, target)
3319            }),
3320        };
3321        self.parser_alias_fallback_files
3322            .fetch_add(files, Ordering::Relaxed);
3323        self.parser_alias_fallback_elapsed_micros.fetch_add(
3324            started.elapsed().as_micros().min(usize::MAX as u128) as usize,
3325            Ordering::Relaxed,
3326        );
3327        matched
3328    }
3329
3330    fn callable_arities_for_target(
3331        &self,
3332        analyzer: &CppGraphSource<'_>,
3333        cpp: &dyn CppSource,
3334        file: &ProjectFile,
3335        prepared: &PreparedSyntaxTree,
3336        spec: &TargetSpec,
3337    ) -> Vec<ActivatedCallableArity> {
3338        let Some(signature) = spec.target.signature() else {
3339            return Vec::new();
3340        };
3341        let Some(candidates) = self
3342            .visible_by_identifier
3343            .get(file)
3344            .and_then(|by_name| by_name.get(&spec.member_name))
3345        else {
3346            return Vec::new();
3347        };
3348        let differing_candidates = candidates
3349            .iter()
3350            .filter(|candidate| {
3351                candidate.is_function()
3352                    && candidate.fq_name() == spec.target.fq_name()
3353                    && candidate.signature() == Some(signature)
3354            })
3355            .filter_map(|candidate| {
3356                analyzer
3357                    .signature_metadata(candidate)
3358                    .into_iter()
3359                    .find_map(|metadata| metadata.callable_arity())
3360                    .filter(|arity| Some(*arity) != spec.callable_arity)
3361                    .map(|arity| (candidate, arity))
3362            })
3363            .collect::<Vec<_>>();
3364        if differing_candidates.is_empty() {
3365            return Vec::new();
3366        }
3367        let mut arities = Vec::with_capacity(differing_candidates.len());
3368        // The activation ranges here describe the whole file rather than one
3369        // reference, so there is no reference guard environment to consult.
3370        let reference = CallableReferenceContext {
3371            file,
3372            position: None,
3373        };
3374        for (candidate, candidate_arity) in differing_candidates {
3375            let declaration_activation = if candidate.source() == file {
3376                callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
3377            } else {
3378                cpp.prepared_syntax(self.token, candidate.source())
3379                    .and_then(|syntax| {
3380                        callable_declaration_activation_in_file(
3381                            analyzer,
3382                            syntax.as_ref(),
3383                            candidate,
3384                            &reference,
3385                        )
3386                    })
3387            };
3388            let Some(declaration_activation) = declaration_activation else {
3389                continue;
3390            };
3391            let activation_byte = if candidate.source() == file {
3392                Some(declaration_activation)
3393            } else {
3394                self.include_activation_for_source(cpp, file, prepared, candidate.source())
3395            };
3396            if let Some(activation_byte) = activation_byte {
3397                arities.push(ActivatedCallableArity {
3398                    activation_byte,
3399                    arity: candidate_arity,
3400                });
3401            }
3402        }
3403        arities
3404    }
3405
3406    fn callable_parameter_macro_arity(
3407        &self,
3408        target: &CodeUnit,
3409        signature: Option<&str>,
3410    ) -> Option<CallableArity> {
3411        let parameter_types = cpp_signature_param_types(signature?)?;
3412        let [macro_name] = parameter_types.as_slice() else {
3413            return None;
3414        };
3415        if macro_name.is_empty()
3416            || !macro_name
3417                .chars()
3418                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
3419        {
3420            return None;
3421        }
3422        let cache_key = (target.source().clone(), macro_name.clone());
3423        if let Some(cached) = self
3424            .callable_parameter_macro_arities
3425            .lock()
3426            .expect("C++ callable parameter-macro arity cache poisoned")
3427            .get(&cache_key)
3428            .copied()
3429        {
3430            return cached;
3431        }
3432        let mut visible_files = HashSet::default();
3433        collect_include_closure(
3434            &self.cpp_source(),
3435            self.cpp.include_target_index(),
3436            target.source(),
3437            &mut visible_files,
3438            None,
3439        );
3440        let mut arities = Vec::new();
3441        for visible_file in visible_files {
3442            let cell = self.macro_event_cell(&visible_file);
3443            for event in
3444                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
3445            {
3446                let MacroEvent::Define { name, binding, .. } = event else {
3447                    continue;
3448                };
3449                if name != macro_name {
3450                    continue;
3451                }
3452                let MacroDefinition::Object { replacement } = &binding.definition else {
3453                    continue;
3454                };
3455                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
3456                    continue;
3457                };
3458                if !arities.contains(&arity) {
3459                    arities.push(arity);
3460                }
3461            }
3462        }
3463        let resolved = (|| {
3464            let required = arities
3465                .iter()
3466                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
3467                .min()?;
3468            let total = arities.iter().map(|arity| arity.total()).max()?;
3469            let repeated = arities
3470                .iter()
3471                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
3472            // Preprocessor conditions can leave more than one object-like parameter
3473            // bundle active in the target header's include closure. Preserve their
3474            // conservative callable envelope instead of choosing whichever definition
3475            // happened to be visited first.
3476            Some(CallableArity::new(required, total, repeated))
3477        })();
3478        self.callable_parameter_macro_arities
3479            .lock()
3480            .expect("C++ callable parameter-macro arity cache poisoned")
3481            .insert(cache_key, resolved);
3482        resolved
3483    }
3484
3485    pub fn include_activation_for_source(
3486        &self,
3487        cpp: &dyn CppSource,
3488        file: &ProjectFile,
3489        prepared: &PreparedSyntaxTree,
3490        donor_source: &ProjectFile,
3491    ) -> Option<usize> {
3492        let key = (file.clone(), donor_source.clone());
3493        if let Some(cached) = self
3494            .include_activation_cells
3495            .lock()
3496            .expect("C++ include activation cache poisoned")
3497            .get(&key)
3498            .copied()
3499        {
3500            return cached;
3501        }
3502        #[cfg(any(test, feature = "test-support"))]
3503        self.include_activation_build_count
3504            .fetch_add(1, Ordering::Relaxed);
3505        let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
3506        let mut cells = self
3507            .include_activation_cells
3508            .lock()
3509            .expect("C++ include activation cache poisoned");
3510        *cells.entry(key).or_insert(activation)
3511    }
3512
3513    pub fn conditional_include_projections_for_source(
3514        &self,
3515        file: &ProjectFile,
3516        prepared: &PreparedSyntaxTree,
3517        donor_source: &ProjectFile,
3518    ) -> Arc<[ConditionalIncludeProjection]> {
3519        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
3520        let cell = self
3521            .conditional_include_projection_cells
3522            .lock()
3523            .expect("C++ conditional include projection cache poisoned")
3524            .entry(file.clone())
3525            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
3526            .clone();
3527        let index = cell.get_or_build_pool_independent(|| {
3528            #[cfg(any(test, feature = "test-support"))]
3529            self.conditional_include_projection_index_build_count
3530                .fetch_add(1, Ordering::Relaxed);
3531            find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
3532                #[cfg(any(test, feature = "test-support"))]
3533                self.conditional_include_projection_state_count
3534                    .fetch_add(1, Ordering::Relaxed);
3535            })
3536        });
3537        index
3538            .get(donor_source)
3539            .cloned()
3540            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
3541    }
3542
3543    #[cfg(any(test, feature = "test-support"))]
3544    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
3545        (
3546            self.conditional_include_projection_index_build_count
3547                .load(Ordering::Relaxed),
3548            self.conditional_include_projection_state_count
3549                .load(Ordering::Relaxed),
3550        )
3551    }
3552
3553    #[cfg(any(test, feature = "test-support"))]
3554    pub fn conditional_include_target_state_count_for_test(&self) -> usize {
3555        self.conditional_include_target_state_count
3556            .load(Ordering::Relaxed)
3557    }
3558
3559    #[cfg(any(test, feature = "test-support"))]
3560    pub fn include_activation_build_count_for_test(&self) -> usize {
3561        self.include_activation_build_count.load(Ordering::Relaxed)
3562    }
3563
3564    #[cfg(any(test, feature = "test-support"))]
3565    pub fn note_using_donor_activation_for_test(&self) {
3566        self.using_donor_activation_count
3567            .fetch_add(1, Ordering::Relaxed);
3568    }
3569
3570    #[cfg(not(any(test, feature = "test-support")))]
3571    pub fn note_using_donor_activation_for_test(&self) {}
3572
3573    #[cfg(any(test, feature = "test-support"))]
3574    pub fn note_using_namespace_lookup_for_test(&self) {
3575        self.using_namespace_lookup_count
3576            .fetch_add(1, Ordering::Relaxed);
3577    }
3578
3579    #[cfg(not(any(test, feature = "test-support")))]
3580    pub fn note_using_namespace_lookup_for_test(&self) {}
3581
3582    #[cfg(any(test, feature = "test-support"))]
3583    pub fn note_using_name_candidate_inspection_for_test(&self) {
3584        self.using_name_candidate_inspection_count
3585            .fetch_add(1, Ordering::Relaxed);
3586    }
3587
3588    #[cfg(not(any(test, feature = "test-support")))]
3589    pub fn note_using_name_candidate_inspection_for_test(&self) {}
3590
3591    #[cfg(any(test, feature = "test-support"))]
3592    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
3593        (
3594            self.using_donor_activation_count.load(Ordering::Relaxed),
3595            self.using_namespace_lookup_count.load(Ordering::Relaxed),
3596            self.callable_reference_spec_build_count
3597                .load(Ordering::Relaxed),
3598            self.using_name_candidate_inspection_count
3599                .load(Ordering::Relaxed),
3600        )
3601    }
3602
3603    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3604        file == target.source()
3605            || self
3606                .visible_by_file
3607                .get(file)
3608                .is_some_and(|visible| visible.contains(target))
3609    }
3610
3611    /// Whether some declaration of `declaration`'s logical symbol is visible at
3612    /// `reference_byte` in `file`.
3613    ///
3614    /// The question is asked of the *logical* symbol, not of the physical unit:
3615    /// an out-of-line body in a `.cpp` nobody includes is never itself visible,
3616    /// and it does not have to be - what makes the call legal is the header
3617    /// declaration that the reference file does include. Reading that relation
3618    /// through `same_logical_callable` rather than through signature strings is
3619    /// the same #2010 correction the gates make, and it matters here because
3620    /// the body and the declaration are exactly the pair that spells one
3621    /// parameter type two ways.
3622    pub fn declaration_visible_at(
3623        &self,
3624        analyzer: &CppGraphSource<'_>,
3625        file: &ProjectFile,
3626        declaration: &CodeUnit,
3627        reference_byte: usize,
3628    ) -> bool {
3629        let reference_guards = OnceCell::new();
3630        self.visible_identifier_candidates(file, declaration.identifier())
3631            .filter(|candidate| {
3632                self.same_logical_callable(analyzer, candidate, declaration)
3633                    || flattened_macro_namespace_declaration_matches(
3634                        analyzer,
3635                        self.cpp,
3636                        file,
3637                        candidate,
3638                        declaration,
3639                        reference_byte,
3640                    )
3641            })
3642            .any(|candidate| {
3643                self.physical_declaration_visible_at(
3644                    analyzer,
3645                    file,
3646                    candidate,
3647                    reference_byte,
3648                    &reference_guards,
3649                )
3650            })
3651    }
3652
3653    /// C forward navigation may bind a call to a later same-file definition.
3654    /// There is no earlier source declaration to activate in that legacy C
3655    /// shape, but the call's preprocessor environment must still imply the
3656    /// definition's requirements. Ordinary C++ and inverse visibility retain
3657    /// the declaration-order rule in [`Self::declaration_visible_at`].
3658    pub fn declaration_visible_for_c_forward_call(
3659        &self,
3660        analyzer: &CppGraphSource<'_>,
3661        file: &ProjectFile,
3662        declaration: &CodeUnit,
3663        reference_byte: usize,
3664    ) -> bool {
3665        if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
3666            return true;
3667        }
3668        if declaration.source() != file {
3669            return false;
3670        }
3671        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3672            return false;
3673        };
3674        let reference_guards = prepared
3675            .tree()
3676            .root_node()
3677            .descendant_for_byte_range(reference_byte, reference_byte)
3678            .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
3679        declaration_guard_requirements(analyzer, self.cpp, declaration)
3680            .into_iter()
3681            .any(|(_, required)| {
3682                guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
3683            })
3684    }
3685
3686    pub fn callable_arity_at_reference(
3687        &self,
3688        analyzer: &CppGraphSource<'_>,
3689        file: &ProjectFile,
3690        candidate: &CodeUnit,
3691        reference_byte: usize,
3692    ) -> Option<CallableArity> {
3693        let key = (file.clone(), logical_symbol_key(candidate));
3694        let cell = self
3695            .callable_reference_specs
3696            .lock()
3697            .expect("C++ callable reference-spec cache poisoned")
3698            .entry(key)
3699            .or_default()
3700            .clone();
3701        let spec = cell.get_or_init(|| {
3702            let prepared = self.cpp.prepared_syntax(self.token, file)?;
3703            let spec = TargetSpec::from_target(analyzer, candidate)?;
3704            let spec = spec
3705                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
3706                .into_owned();
3707            #[cfg(any(test, feature = "test-support"))]
3708            self.callable_reference_spec_build_count
3709                .fetch_add(1, Ordering::Relaxed);
3710            Some(spec)
3711        });
3712        spec.as_ref()?.callable_arity_at(reference_byte)
3713    }
3714
3715    fn physical_declaration_visible_at(
3716        &self,
3717        analyzer: &CppGraphSource<'_>,
3718        file: &ProjectFile,
3719        declaration: &CodeUnit,
3720        reference_byte: usize,
3721        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
3722    ) -> bool {
3723        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3724            return false;
3725        };
3726        let reference = CallableReferenceContext {
3727            file,
3728            position: Some(CallableReferencePosition {
3729                prepared: prepared.as_ref(),
3730                byte: reference_byte,
3731                guards: reference_guards,
3732            }),
3733        };
3734        if declaration.source() == file {
3735            return callable_declaration_activation_in_file(
3736                analyzer,
3737                prepared.as_ref(),
3738                declaration,
3739                &reference,
3740            )
3741            .or_else(|| {
3742                self.exhaustive_guard_family_activation(
3743                    analyzer,
3744                    prepared.as_ref(),
3745                    declaration,
3746                    &reference,
3747                )
3748            })
3749            .is_some_and(|activation| activation < reference_byte);
3750        }
3751        let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
3752            return false;
3753        };
3754        if callable_declaration_activation_in_file(
3755            analyzer,
3756            donor_syntax.as_ref(),
3757            declaration,
3758            &reference,
3759        )
3760        .or_else(|| {
3761            self.exhaustive_guard_family_activation(
3762                analyzer,
3763                donor_syntax.as_ref(),
3764                declaration,
3765                &reference,
3766            )
3767        })
3768        .is_none()
3769        {
3770            return false;
3771        }
3772        declaration_guard_requirements(analyzer, self.cpp, declaration)
3773            .into_iter()
3774            .any(|(_, declaration_guards)| {
3775                self.foreign_declaration_reachable_at_reference(
3776                    file,
3777                    prepared.as_ref(),
3778                    declaration.source(),
3779                    &declaration_guards,
3780                    reference.guards(),
3781                    reference_byte,
3782                )
3783            })
3784    }
3785
3786    pub fn external_type_candidate_visible_at(
3787        &self,
3788        file: &ProjectFile,
3789        candidate: &CodeUnit,
3790        reference_byte: usize,
3791    ) -> bool {
3792        if candidate.source() == file {
3793            return true;
3794        }
3795        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3796            return false;
3797        };
3798        self.visible_identifier_candidates(file, candidate.identifier())
3799            .filter(|peer| same_logical_symbol(candidate, peer))
3800            .any(|peer| {
3801                peer.source() == file
3802                    || self
3803                        .include_activation_for_source(
3804                            self.cpp,
3805                            file,
3806                            prepared.as_ref(),
3807                            peer.source(),
3808                        )
3809                        .is_some_and(|activation| activation <= reference_byte)
3810            })
3811    }
3812
3813    pub fn external_type_declaration_visible_at(
3814        &self,
3815        file: &ProjectFile,
3816        candidate: &CodeUnit,
3817        reference_byte: usize,
3818    ) -> bool {
3819        if candidate.source() == file {
3820            return true;
3821        }
3822        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3823            return false;
3824        };
3825        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
3826            .is_some_and(|activation| activation <= reference_byte)
3827    }
3828
3829    /// The preprocessor facts the build proves for a reference sited in
3830    /// `file` (#2011).
3831    ///
3832    /// Every `-D` that survives its command's `-D`/`-U` ordering is a positive
3833    /// `Defined` fact, and a fact holds only when every compile configuration
3834    /// that governs the file agrees on it (intersection). The facts are
3835    /// strictly additive to the reference's active guard set: they can prove a
3836    /// required guard, but the guard check itself is never weakened and no
3837    /// implication is ever inferred from source text.
3838    ///
3839    /// A file with its own database entry answers from that entry alone
3840    /// (phase 1). A header takes its context from the translation units whose
3841    /// include closure reaches it, intersected across all of them (phase 2):
3842    /// the header is compiled once per including TU, so a fact holds for a
3843    /// header-sited reference only when every one of those compilations
3844    /// proves it. A reaching TU the database does not cover proves nothing,
3845    /// which empties the intersection. A file nothing covers or reaches has
3846    /// no facts and every check runs on source structure alone.
3847    pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
3848        if let Some(cached) = self
3849            .compile_proven_guard_cells
3850            .lock()
3851            .expect("C++ compile-proven guard cache poisoned")
3852            .get(file)
3853        {
3854            return Arc::clone(cached);
3855        }
3856        let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
3857            Some(names) => names,
3858            None => {
3859                let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
3860                let seed = translation_units.next().and_then(|translation_unit| {
3861                    context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
3862                });
3863                match seed {
3864                    None => HashSet::default(),
3865                    Some(mut names) => {
3866                        for translation_unit in translation_units {
3867                            let Some(reached) = context_fact_names(
3868                                self.cpp.compile_contexts_for(&translation_unit),
3869                            ) else {
3870                                names.clear();
3871                                break;
3872                            };
3873                            names.retain(|name| reached.contains(name));
3874                            if names.is_empty() {
3875                                break;
3876                            }
3877                        }
3878                        names
3879                    }
3880                }
3881            }
3882        };
3883        let proven = Arc::new(
3884            names
3885                .into_iter()
3886                .map(PreprocessorGuard::Defined)
3887                .collect::<HashSet<_>>(),
3888        );
3889        self.compile_proven_guard_cells
3890            .lock()
3891            .expect("C++ compile-proven guard cache poisoned")
3892            .insert(file.clone(), Arc::clone(&proven));
3893        proven
3894    }
3895
3896    /// Whether no compile data covers the compilations of `file`: it has no
3897    /// database entry of its own, and either nothing reaches it or some
3898    /// translation unit that reaches it has no entry. This is the state a
3899    /// regenerated `compile_commands.json` could decide; data that is present
3900    /// for every governing compilation but does not prove a guard is a
3901    /// decided conservative miss, not this state.
3902    fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
3903        if !self.cpp.compile_contexts_for(file).is_empty() {
3904            return false;
3905        }
3906        let translation_units = self.cpp.reaching_translation_units(file);
3907        translation_units.is_empty()
3908            || translation_units
3909                .iter()
3910                .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
3911    }
3912
3913    /// Whether a lookup miss for `identifier` in `file` is explainable by
3914    /// missing compile context (#2011): some same-name declaration is
3915    /// reachable through a conditional include whose required guards neither
3916    /// contradict the reference's active guards nor follow from them, and the
3917    /// translation unit has no compile-commands entry that could decide the
3918    /// question. Callers surface this as an explicit "requires compile
3919    /// context" incompleteness instead of an indistinguishable miss.
3920    ///
3921    /// A structurally disproven declaration (contradicting guards) and a TU
3922    /// whose compile context exists but does not prove the guard both answer
3923    /// `false`: those misses are decided, not incomplete.
3924    pub fn miss_requires_compile_context(
3925        &self,
3926        file: &ProjectFile,
3927        identifier: &str,
3928        reference: Node<'_>,
3929    ) -> bool {
3930        if !self.compile_context_is_absent(file) {
3931            return false;
3932        }
3933        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3934            return false;
3935        };
3936        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3937        let reference_byte = reference.start_byte();
3938        let mut sources = self
3939            .visible_identifier_candidates(file, identifier)
3940            .map(CodeUnit::source)
3941            .filter(|source| *source != file)
3942            .collect::<Vec<_>>();
3943        sources.sort();
3944        sources.dedup();
3945        sources.into_iter().any(|declaration_source| {
3946            self.conditional_include_projections_for_source(
3947                file,
3948                prepared.as_ref(),
3949                declaration_source,
3950            )
3951            .iter()
3952            .any(|projection| {
3953                projection.activation_byte <= reference_byte
3954                    && !guard_requirements_hold_at_reference(
3955                        &projection.required_guards,
3956                        reference_guards.as_ref(),
3957                    )
3958                    && guards_compatible_at_reference(
3959                        &projection.required_guards,
3960                        reference_guards.as_ref(),
3961                    )
3962            })
3963        })
3964    }
3965
3966    /// Decide whether a declaration that lives in another file reaches a
3967    /// reference in `file`.
3968    ///
3969    /// An external header selects its declaration branch before the reference
3970    /// file is parsed. Require compatible reference guards, but do not test
3971    /// the header's guard expression for stability in the reference file: a
3972    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
3973    /// wraps every declaration of a portable C header, and demanding it would
3974    /// hide the whole header. Guards that the reference file imposes on its
3975    /// own `#include` still have to hold, and still have to be stable.
3976    fn foreign_declaration_reachable_at_reference(
3977        &self,
3978        file: &ProjectFile,
3979        prepared: &PreparedSyntaxTree,
3980        declaration_source: &ProjectFile,
3981        declaration_guards: &HashSet<PreprocessorGuard>,
3982        reference_guards: Option<&HashSet<PreprocessorGuard>>,
3983        reference_byte: usize,
3984    ) -> bool {
3985        // The translation unit's build-proven defines join the reference's
3986        // active guard set (#2011): a conditional include like the nng
3987        // `NNG_PLATFORM_POSIX` chain is provable only by the compile command.
3988        // A reference whose own environment is unknown stays unknown -- the
3989        // facts extend an environment, they never invent one.
3990        let proven = self.compile_proven_guards(file);
3991        let augmented;
3992        let reference_guards = match reference_guards {
3993            Some(active) if !proven.is_empty() => {
3994                augmented = active.union(&proven).cloned().collect();
3995                Some(&augmented)
3996            }
3997            other => other,
3998        };
3999        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
4000            return false;
4001        }
4002        if self
4003            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
4004            .is_some_and(|activation| activation <= reference_byte)
4005        {
4006            return true;
4007        }
4008        let projections =
4009            self.conditional_include_projections_for_source(file, prepared, declaration_source);
4010        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4011            eprintln!(
4012                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
4013                declaration_source.rel_path().display(),
4014                declaration_guards.len(),
4015                proven.len(),
4016                projections.len(),
4017            );
4018        }
4019        projections.iter().any(|projection| {
4020            projection.activation_byte <= reference_byte
4021                && guard_requirements_hold_at_reference(
4022                    &projection.required_guards,
4023                    reference_guards,
4024                )
4025                && self.preprocessor_guards_stable_between(
4026                    file,
4027                    projection.activation_byte,
4028                    reference_byte,
4029                    &projection.required_guards,
4030                )
4031        })
4032    }
4033
4034    fn foreign_declaration_may_be_reachable_from_raw_guards(
4035        &self,
4036        file: &ProjectFile,
4037        prepared: &PreparedSyntaxTree,
4038        declaration_source: &ProjectFile,
4039        declaration_guards: &HashSet<PreprocessorGuard>,
4040        reference_guards: Option<&HashSet<PreprocessorGuard>>,
4041        reference_byte: usize,
4042    ) -> bool {
4043        let proven = self.compile_proven_guards(file);
4044        let augmented;
4045        let reference_guards = match reference_guards {
4046            Some(active) if !proven.is_empty() => {
4047                augmented = active.union(&proven).cloned().collect();
4048                Some(&augmented)
4049            }
4050            other => other,
4051        };
4052        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
4053            return false;
4054        }
4055        if self
4056            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
4057            .is_some_and(|activation| activation <= reference_byte)
4058        {
4059            return true;
4060        }
4061        let reachable = find_conditional_include_projection_for_source(
4062            self.cpp,
4063            self.token,
4064            file,
4065            prepared,
4066            declaration_source,
4067            reference_guards,
4068            reference_byte,
4069            &|| {
4070                #[cfg(any(test, feature = "test-support"))]
4071                self.conditional_include_target_state_count
4072                    .fetch_add(1, Ordering::Relaxed);
4073            },
4074        );
4075        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4076            eprintln!(
4077                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
4078                declaration_source.rel_path().display(),
4079                declaration_guards.len(),
4080                proven.len(),
4081                reference_guards.map_or(0, HashSet::len),
4082            );
4083        }
4084        reachable
4085    }
4086
4087    fn foreign_declaration_reachable_from_compile_proven_guards(
4088        &self,
4089        file: &ProjectFile,
4090        prepared: &PreparedSyntaxTree,
4091        declaration_source: &ProjectFile,
4092        declaration_guards: &HashSet<PreprocessorGuard>,
4093        reference_byte: usize,
4094    ) -> bool {
4095        let proven = self.compile_proven_guards(file);
4096        if proven.is_empty()
4097            || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
4098        {
4099            return false;
4100        }
4101        let projections =
4102            self.conditional_include_projections_for_source(file, prepared, declaration_source);
4103        if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
4104            eprintln!(
4105                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
4106                declaration_source.rel_path().display(),
4107                declaration_guards.len(),
4108                proven.len(),
4109                projections.len(),
4110            );
4111        }
4112        projections.iter().any(|projection| {
4113            projection.activation_byte <= reference_byte
4114                    && guard_requirements_hold_at_reference(
4115                        &projection.required_guards,
4116                        Some(proven.as_ref()),
4117                    )
4118                    // Build facts hold at translation-unit entry. A source
4119                    // `#undef` or an earlier include may invalidate one before
4120                    // this conditional include is reached; mutations after the
4121                    // include cannot revoke declarations it already supplied.
4122                    && self.preprocessor_guards_stable_between(
4123                        file,
4124                        0,
4125                        projection.activation_byte,
4126                        &projection.required_guards,
4127                    )
4128        })
4129    }
4130
4131    pub fn external_type_candidate_visible_in_context(
4132        &self,
4133        analyzer: &CppGraphSource<'_>,
4134        file: &ProjectFile,
4135        candidate: &CodeUnit,
4136        reference: Node<'_>,
4137    ) -> bool {
4138        let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
4139        if report_stats {
4140            eprintln!(
4141                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
4142                candidate.fq_name(),
4143                candidate.source().rel_path().display(),
4144                file.rel_path().display(),
4145                reference.start_byte(),
4146            );
4147        }
4148        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4149            return false;
4150        };
4151        let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
4152        let reference_guards = OnceCell::new();
4153        let reference_guards_at_site = || {
4154            reference_guards.get_or_init(|| {
4155                let started = Instant::now();
4156                if report_stats {
4157                    eprintln!(
4158                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
4159                        file.rel_path().display(),
4160                        reference.start_byte(),
4161                        raw_reference_guards.as_ref().map_or(0, HashSet::len),
4162                    );
4163                }
4164                let macro_environment = self.macro_environment(file, reference.start_byte());
4165                let filtered = raw_reference_guards
4166                    .clone()
4167                    .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
4168                if report_stats {
4169                    eprintln!(
4170                        "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
4171                        filtered.is_some(),
4172                        started.elapsed().as_millis(),
4173                    );
4174                }
4175                filtered
4176            })
4177        };
4178
4179        let peers = self
4180            .visible_identifier_candidates(file, candidate.identifier())
4181            .filter(|peer| same_logical_symbol(candidate, peer))
4182            .collect::<Vec<_>>();
4183        if report_stats {
4184            let peer_sources = peers
4185                .iter()
4186                .map(|peer| peer.source().rel_path().display().to_string())
4187                .collect::<Vec<_>>();
4188            eprintln!(
4189                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
4190                candidate.fq_name(),
4191            );
4192        }
4193        let directly_visible_without_reference_environment = peers.iter().any(|peer| {
4194            declaration_guard_requirements(analyzer, self.cpp, peer)
4195                .into_iter()
4196                .any(|(declaration_byte, declaration_guards)| {
4197                    if peer.source() == file {
4198                        let visible = declaration_byte < reference.start_byte()
4199                            && declaration_guards.is_empty();
4200                        if report_stats {
4201                            eprintln!(
4202                                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
4203                                peer.source().rel_path().display(),
4204                                declaration_guards.len(),
4205                            );
4206                        }
4207                        return visible;
4208                    }
4209                    let direct = declaration_guards.is_empty()
4210                        && self
4211                            .include_activation_for_source(
4212                                self.cpp,
4213                                file,
4214                                prepared.as_ref(),
4215                                peer.source(),
4216                            )
4217                            .is_some_and(|activation| activation <= reference.start_byte());
4218                    let compile_proven = !direct
4219                        && self.foreign_declaration_reachable_from_compile_proven_guards(
4220                            file,
4221                            prepared.as_ref(),
4222                            peer.source(),
4223                            &declaration_guards,
4224                            reference.start_byte(),
4225                        );
4226                    if report_stats {
4227                        eprintln!(
4228                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
4229                            peer.source().rel_path().display(),
4230                            declaration_guards.len(),
4231                        );
4232                    }
4233                    direct || compile_proven
4234                })
4235        });
4236        if directly_visible_without_reference_environment {
4237            if report_stats {
4238                eprintln!(
4239                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
4240                    candidate.fq_name(),
4241                );
4242            }
4243            return true;
4244        }
4245        let directly_visible = peers.iter().any(|peer| {
4246            declaration_guard_requirements(analyzer, self.cpp, peer)
4247                .into_iter()
4248                .any(|(declaration_byte, declaration_guards)| {
4249                    if peer.source() == file {
4250                        if declaration_byte >= reference.start_byte() {
4251                            return false;
4252                        }
4253                        if !guard_requirements_hold_at_reference(
4254                            &declaration_guards,
4255                            raw_reference_guards.as_ref(),
4256                        ) {
4257                            return false;
4258                        }
4259                        return guard_requirements_hold_at_reference(
4260                            &declaration_guards,
4261                            reference_guards_at_site().as_ref(),
4262                        ) && self.preprocessor_guards_stable_between(
4263                            file,
4264                            declaration_byte,
4265                            reference.start_byte(),
4266                            &declaration_guards,
4267                        );
4268                    }
4269                    let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
4270                        file,
4271                        prepared.as_ref(),
4272                        peer.source(),
4273                        &declaration_guards,
4274                        raw_reference_guards.as_ref(),
4275                        reference.start_byte(),
4276                    );
4277                    if report_stats {
4278                        eprintln!(
4279                            "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
4280                            peer.source().rel_path().display(),
4281                            declaration_guards.len(),
4282                        );
4283                    }
4284                    if !raw_feasible {
4285                        return false;
4286                    }
4287                    self.foreign_declaration_reachable_at_reference(
4288                        file,
4289                        prepared.as_ref(),
4290                        peer.source(),
4291                        &declaration_guards,
4292                        reference_guards_at_site().as_ref(),
4293                        reference.start_byte(),
4294                    )
4295                })
4296        });
4297        if directly_visible {
4298            if report_stats {
4299                eprintln!(
4300                    "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
4301                    candidate.fq_name(),
4302                );
4303            }
4304            return true;
4305        }
4306        let complementary = self
4307            .visible_identifier_candidates(file, candidate.identifier())
4308            .filter(|peer| {
4309                peer.kind() == candidate.kind()
4310                    && peer.fq_name() == candidate.fq_name()
4311                    && peer.source() == candidate.source()
4312            })
4313            .collect::<Vec<_>>();
4314        // A completed #if/#else family declares the shared source-level name
4315        // before this reference. A later macro mutation cannot revoke that
4316        // declaration. The family gate below rejects declarations split across
4317        // separate conditional blocks, where mutation can change coverage.
4318        let complementary_family =
4319            self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
4320        let raw_candidate_branch_compatible = complementary_family
4321            && raw_reference_guards.as_ref().is_some_and(|active| {
4322                declaration_guard_requirements(analyzer, self.cpp, candidate)
4323                    .iter()
4324                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
4325            });
4326        if report_stats {
4327            eprintln!(
4328                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
4329                candidate.fq_name(),
4330                complementary.len(),
4331                complementary_family,
4332                raw_candidate_branch_compatible,
4333            );
4334        }
4335        let candidate_branch_compatible = raw_candidate_branch_compatible
4336            && reference_guards_at_site().as_ref().is_some_and(|active| {
4337                declaration_guard_requirements(analyzer, self.cpp, candidate)
4338                    .iter()
4339                    .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
4340            });
4341        let complementary_visible = candidate_branch_compatible
4342            && if candidate.source() == file {
4343                declaration_guard_requirements(analyzer, self.cpp, candidate)
4344                    .iter()
4345                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
4346            } else {
4347                self.include_activation_for_source(
4348                    self.cpp,
4349                    file,
4350                    prepared.as_ref(),
4351                    candidate.source(),
4352                )
4353                .is_some_and(|activation| activation <= reference.start_byte())
4354            };
4355        if report_stats {
4356            eprintln!(
4357                "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
4358                if complementary_visible {
4359                    "complementary"
4360                } else {
4361                    "missing"
4362                },
4363                candidate.fq_name(),
4364            );
4365        }
4366        complementary_visible
4367    }
4368
4369    pub fn is_exhaustive_same_fqn_type_declaration_family(
4370        &self,
4371        analyzer: &CppGraphSource<'_>,
4372        file: &ProjectFile,
4373        candidate: &CodeUnit,
4374    ) -> bool {
4375        let candidates = self
4376            .visible_identifier_candidates(file, candidate.identifier())
4377            .filter(|peer| {
4378                peer.kind() == candidate.kind()
4379                    && peer.fq_name() == candidate.fq_name()
4380                    && peer.source() == candidate.source()
4381            })
4382            .collect::<Vec<_>>();
4383        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
4384    }
4385
4386    /// Prove a nested type alias used as a dependent member-pointer owner when
4387    /// its owning class has mutually-exclusive declarations.  A common C++11
4388    /// compatibility shape provides the owning class in one preprocessor
4389    /// branch and aliases it to a standard-library type in the other branch;
4390    /// the nested fallback alias is therefore not itself active in every
4391    /// branch even though the qualified owner API is.
4392    ///
4393    /// This is deliberately narrower than ordinary type visibility.  The
4394    /// caller has already recovered a member-pointer owner path from the CST;
4395    /// this helper additionally requires the target's structured parent to
4396    /// match that path, physical source visibility, and exact preprocessor
4397    /// guard agreement with the parent declaration.  Only then may the
4398    /// parent's direct/complementary same-FQN visibility stand in for the
4399    /// nested terminal's active-branch check.
4400    pub fn dependent_member_pointer_alias_visible_in_context(
4401        &self,
4402        analyzer: &CppGraphSource<'_>,
4403        file: &ProjectFile,
4404        candidate: &CodeUnit,
4405        owner_components: &[String],
4406        reference: Node<'_>,
4407    ) -> bool {
4408        if !analyzer
4409            .type_alias_provider()
4410            .is_some_and(|provider| provider.is_type_alias(candidate))
4411        {
4412            return false;
4413        }
4414        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
4415            return false;
4416        };
4417        if terminal != candidate.identifier()
4418            || canonical_cpp_scope_components(candidate) != owner_components
4419        {
4420            return false;
4421        }
4422        let Some(expected_parent_fq_name) =
4423            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
4424        else {
4425            return false;
4426        };
4427        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
4428            return false;
4429        };
4430        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
4431            || parent_anchor.source() != candidate.source()
4432            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
4433        {
4434            return false;
4435        }
4436
4437        // The ordinary path already handles unguarded aliases (and preserves
4438        // same-file declaration ordering).  This fallback is only for a
4439        // physically visible declaration whose guard is the owning branch's
4440        // guard, so reject a same-file declaration that appears after the
4441        // reference before considering guard compatibility.
4442        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
4443            || candidate.source() == file
4444                && !analyzer
4445                    .ranges(candidate)
4446                    .iter()
4447                    .any(|range| range.start_byte < reference.start_byte())
4448        {
4449            return false;
4450        }
4451
4452        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
4453        if candidate_guards.is_empty() {
4454            return false;
4455        }
4456        let same_guard_sets =
4457            |left: &[(usize, HashSet<PreprocessorGuard>)],
4458             right: &[(usize, HashSet<PreprocessorGuard>)]| {
4459                left.iter().all(|(_, left_guards)| {
4460                    right
4461                        .iter()
4462                        .any(|(_, right_guards)| left_guards == right_guards)
4463                })
4464            };
4465        let parent_candidates = self
4466            .visible_identifier_candidates(file, parent_anchor.identifier())
4467            .filter(|peer| {
4468                peer.kind() == parent_anchor.kind()
4469                    && peer.fq_name() == expected_parent_fq_name.as_str()
4470                    && peer.source() == parent_anchor.source()
4471                    && canonical_cpp_scope_components(peer) == owner_prefix
4472            })
4473            .filter_map(|peer| {
4474                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
4475                (candidate_guards.len() == parent_guards.len()
4476                    && same_guard_sets(&candidate_guards, &parent_guards)
4477                    && same_guard_sets(&parent_guards, &candidate_guards))
4478                .then(|| (peer.clone(), parent_guards))
4479            })
4480            .collect::<Vec<_>>();
4481        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
4482            return false;
4483        };
4484
4485        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4486            return false;
4487        };
4488        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
4489        else {
4490            return false;
4491        };
4492        // An external header selects its declaration branch before the
4493        // reference file is parsed. Require compatible reference guards, but
4494        // do not test the header's guard expression for stability in the
4495        // reference file. Same-file aliases still require that stability.
4496        if !candidate_guards.iter().any(|(_, target_guards)| {
4497            guards_compatible_at_reference(target_guards, Some(&reference_guards))
4498                && (candidate.source() != file
4499                    || self.preprocessor_guards_stable_between(
4500                        file,
4501                        0,
4502                        reference.start_byte(),
4503                        target_guards,
4504                    ))
4505        }) {
4506            return false;
4507        }
4508
4509        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
4510    }
4511
4512    /// Check a type candidate's preprocessor/import context without imposing
4513    /// ordinary declaration-before-reference ordering for same-file peers.
4514    ///
4515    /// C++ class scope makes member names visible throughout the complete
4516    /// class, including a trailing return type that appears before the member
4517    /// alias declaration in source order. Callers must first prove that the
4518    /// reference is inside the candidate's indexed class owner; this helper
4519    /// only relaxes the byte-order predicate while retaining guard and include
4520    /// activation checks.
4521    pub fn external_type_candidate_guard_compatible_in_context(
4522        &self,
4523        analyzer: &CppGraphSource<'_>,
4524        file: &ProjectFile,
4525        candidate: &CodeUnit,
4526        reference: Node<'_>,
4527    ) -> bool {
4528        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4529            return false;
4530        };
4531        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4532
4533        self.visible_identifier_candidates(file, candidate.identifier())
4534            .filter(|peer| same_logical_symbol(candidate, peer))
4535            .any(|peer| {
4536                declaration_guard_requirements(analyzer, self.cpp, peer)
4537                    .into_iter()
4538                    .any(|(declaration_byte, declaration_guards)| {
4539                        if peer.source() == file {
4540                            let (start, end) = if declaration_byte <= reference.start_byte() {
4541                                (declaration_byte, reference.start_byte())
4542                            } else {
4543                                (reference.start_byte(), declaration_byte)
4544                            };
4545                            return guard_requirements_hold_at_reference(
4546                                &declaration_guards,
4547                                reference_guards.as_ref(),
4548                            ) && self.preprocessor_guards_stable_between(
4549                                file,
4550                                start,
4551                                end,
4552                                &declaration_guards,
4553                            );
4554                        }
4555                        self.foreign_declaration_reachable_at_reference(
4556                            file,
4557                            prepared.as_ref(),
4558                            peer.source(),
4559                            &declaration_guards,
4560                            reference_guards.as_ref(),
4561                            reference.start_byte(),
4562                        )
4563                    })
4564            })
4565    }
4566
4567    /// Whether a same-file callable declaration is nameable from `reference`
4568    /// after deliberately relaxing declaration-before-reference ordering.
4569    ///
4570    /// Ordinary lookup still requires an earlier declaration. Definition
4571    /// navigation for incomplete C translation units may recover a later
4572    /// definition, but only when it is at file scope and its preprocessor
4573    /// requirements hold at the call (#2404).
4574    pub fn same_file_callable_guard_compatible_ignoring_order(
4575        &self,
4576        analyzer: &CppGraphSource<'_>,
4577        file: &ProjectFile,
4578        candidate: &CodeUnit,
4579        reference: Node<'_>,
4580    ) -> bool {
4581        if candidate.source() != file || !candidate.is_callable() {
4582            return false;
4583        }
4584        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4585            return false;
4586        };
4587        let guards = OnceCell::new();
4588        let context = CallableReferenceContext {
4589            file,
4590            position: Some(CallableReferencePosition {
4591                prepared: prepared.as_ref(),
4592                byte: reference.start_byte(),
4593                guards: &guards,
4594            }),
4595        };
4596        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
4597            .into_iter()
4598            .any(|declaration| {
4599                callable_preprocessor_context_is_visible_for_reference(
4600                    declaration,
4601                    prepared.source(),
4602                    &context,
4603                )
4604            })
4605    }
4606
4607    pub fn type_candidate_may_be_visible_before_reference(
4608        &self,
4609        analyzer: &CppGraphSource<'_>,
4610        file: &ProjectFile,
4611        candidate: &CodeUnit,
4612        reference_byte: usize,
4613    ) -> bool {
4614        let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4615            return false;
4616        };
4617        let root = prepared.tree().root_node();
4618        let end_byte = reference_byte
4619            .saturating_add(1)
4620            .min(prepared.source().len());
4621        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
4622            return false;
4623        };
4624        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
4625    }
4626
4627    pub fn preprocessor_guards_stable_between(
4628        &self,
4629        file: &ProjectFile,
4630        start_byte: usize,
4631        end_byte: usize,
4632        guards: &HashSet<PreprocessorGuard>,
4633    ) -> bool {
4634        if guards.is_empty() || start_byte >= end_byte {
4635            return true;
4636        }
4637        let cell = self.macro_event_cell(file);
4638        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4639        let mut visited = HashSet::from_iter([file.clone()]);
4640        !events.iter().any(|event| {
4641            event.byte() >= start_byte
4642                && event.byte() < end_byte
4643                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
4644        })
4645    }
4646
4647    fn macro_event_may_mutate_guards(
4648        &self,
4649        event: &MacroEvent,
4650        guards: &HashSet<PreprocessorGuard>,
4651        visited: &mut HashSet<ProjectFile>,
4652    ) -> bool {
4653        match event {
4654            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
4655                guards.iter().any(|guard| guard.may_depend_on_macro(name))
4656            }
4657            MacroEvent::Include { targets, .. } => {
4658                targets.is_empty()
4659                    || targets
4660                        .iter()
4661                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
4662            }
4663            MacroEvent::Invalidate { .. } => true,
4664        }
4665    }
4666
4667    fn source_may_mutate_guards(
4668        &self,
4669        file: &ProjectFile,
4670        guards: &HashSet<PreprocessorGuard>,
4671        visited: &mut HashSet<ProjectFile>,
4672    ) -> bool {
4673        if !visited.insert(file.clone()) {
4674            return false;
4675        }
4676        let cell = self.macro_event_cell(file);
4677        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4678        events
4679            .iter()
4680            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
4681    }
4682
4683    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
4684        let normalized = normalize_reference_name(raw_name)?;
4685        self.type_candidates(file, &normalized)
4686            .into_iter()
4687            .next()
4688            .cloned()
4689    }
4690
4691    /// Mirror forward navigation's visible-name fallback for a bare parameter
4692    /// type after lexical owner and inheritance lookup is exhausted.
4693    ///
4694    /// Generated or otherwise unindexed base classes can hide the alias that
4695    /// makes a parameter type valid C++. Accept the fallback only when every
4696    /// include-visible class or alias with that spelling canonicalizes to one
4697    /// logical type. A shadowing local type resolves lexically before this
4698    /// path, while distinct visible types keep the result ambiguous.
4699    pub fn unique_visible_parameter_type_fallback(
4700        &self,
4701        analyzer: &CppGraphSource<'_>,
4702        file: &ProjectFile,
4703        node: Node<'_>,
4704        source: &str,
4705    ) -> Option<CodeUnit> {
4706        if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
4707            return None;
4708        }
4709        let name = node_text(node, source);
4710        let candidates = self
4711            .visible_identifier_candidates(file, name)
4712            .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
4713            .filter(|candidate| {
4714                self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
4715            })
4716            .collect::<Vec<_>>();
4717        self.unique_canonical_type_candidate(analyzer, file, &candidates)
4718    }
4719
4720    pub fn resolve_type_node_result(
4721        &self,
4722        file: &ProjectFile,
4723        node: Node<'_>,
4724        source: &str,
4725    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
4726        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
4727            return Ok(None);
4728        };
4729        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
4730            return Ok(Some(primary));
4731        };
4732        self.resolve_template_arguments(file, primary, &arguments)
4733            .map(Some)
4734    }
4735
4736    pub fn resolve_type_node_primary(
4737        &self,
4738        file: &ProjectFile,
4739        node: Node<'_>,
4740        source: &str,
4741    ) -> Option<CodeUnit> {
4742        let components = cpp_type_name_components(node, source)?;
4743        self.resolve_type(file, &components.join("::"))
4744    }
4745
4746    pub fn resolve_template_arguments(
4747        &self,
4748        file: &ProjectFile,
4749        primary: CodeUnit,
4750        arguments: &[CppTemplateExpression],
4751    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
4752        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
4753    }
4754
4755    fn resolve_template_arguments_inner(
4756        &self,
4757        file: &ProjectFile,
4758        primary: CodeUnit,
4759        arguments: &[CppTemplateExpression],
4760        seen_aliases: &mut HashSet<CodeUnit>,
4761    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
4762        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
4763            && let Some(alias_target) = &metadata.alias_target
4764        {
4765            if !seen_aliases.insert(primary.clone()) {
4766                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
4767            }
4768            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
4769                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
4770            let target_name = alias_target.components.join("::");
4771            let target_primary = if alias_target.global {
4772                unique_logical_type_candidate(self.type_candidates(file, &target_name))
4773            } else {
4774                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
4775            };
4776            let Some(target_primary) = target_primary else {
4777                // A dependent or external RHS cannot be canonicalized from the
4778                // indexed graph. Preserve the alias's direct identity instead
4779                // of inventing a target from its source spelling.
4780                return Ok(primary);
4781            };
4782            let Some(target_arguments) = &alias_target.arguments else {
4783                return Ok(target_primary);
4784            };
4785            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
4786                .ok_or(CppTemplateResolutionError::Substitution)?;
4787            return self.resolve_template_arguments_inner(
4788                file,
4789                target_primary,
4790                &target_arguments,
4791                seen_aliases,
4792            );
4793        }
4794
4795        let primary_fq_name = self
4796            .cpp_template_metadata
4797            .get(&primary)
4798            .map(|metadata| metadata.primary_fq_name.clone())
4799            .unwrap_or_else(|| primary.fq_name());
4800        let has_specialization_metadata = self
4801            .cpp_template_families
4802            .get(&primary_fq_name)
4803            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
4804        if !has_specialization_metadata {
4805            return Ok(primary);
4806        }
4807        self.select_template_specialization(file, &primary, arguments)
4808    }
4809
4810    fn select_template_specialization(
4811        &self,
4812        file: &ProjectFile,
4813        resolved: &CodeUnit,
4814        explicit_arguments: &[CppTemplateExpression],
4815    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
4816        let primary_fq_name = self
4817            .cpp_template_metadata
4818            .get(resolved)
4819            .map(|metadata| metadata.primary_fq_name.clone())
4820            .unwrap_or_else(|| resolved.fq_name());
4821        let family = self
4822            .cpp_template_families
4823            .get(&primary_fq_name)
4824            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
4825        let primary_candidates = family
4826            .iter()
4827            .filter_map(|unit| {
4828                let metadata = self.cpp_template_metadata.get(unit)?;
4829                (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
4830            })
4831            .collect::<Vec<_>>();
4832        let primary_unit = primary_candidates
4833            .iter()
4834            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
4835            .or_else(|| {
4836                primary_candidates
4837                    .iter()
4838                    .map(|(unit, _)| *unit)
4839                    .min_by_key(|unit| {
4840                        (
4841                            unit.source().to_string(),
4842                            unit.signature().unwrap_or_default(),
4843                        )
4844                    })
4845            })
4846            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
4847        let primary_parameters =
4848            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
4849                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
4850        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
4851            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
4852
4853        let mut applicable = Vec::new();
4854        for unit in family {
4855            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
4856                continue;
4857            };
4858            if metadata.is_primary() || !self.is_visible(file, unit) {
4859                continue;
4860            }
4861            if !cpp_specialization_matches(metadata, &expanded) {
4862                continue;
4863            }
4864            applicable.push((unit, metadata));
4865        }
4866        if applicable.is_empty() {
4867            return Ok(primary_unit.clone());
4868        }
4869
4870        // A scalar constraint count cannot represent C++ partial ordering:
4871        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
4872        // Select only a logical candidate whose structural pattern is strictly
4873        // more specialized than every other distinct applicable candidate.
4874        let winners = applicable
4875            .iter()
4876            .filter(|(candidate, candidate_metadata)| {
4877                applicable.iter().all(|(other, other_metadata)| {
4878                    same_visible_symbol(candidate, other)
4879                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
4880                })
4881            })
4882            .copied()
4883            .collect::<Vec<_>>();
4884        let Some((selected, _)) = winners.first() else {
4885            // Mutually incomparable applicable candidates: every one of them
4886            // is a live contender.
4887            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
4888                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
4889            });
4890        };
4891        if winners
4892            .iter()
4893            .any(|(unit, _)| !same_visible_symbol(unit, selected))
4894        {
4895            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
4896                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
4897            });
4898        }
4899        Ok((*selected).clone())
4900    }
4901
4902    pub fn resolve_type_components_lexically(
4903        &self,
4904        analyzer: &CppGraphSource<'_>,
4905        file: &ProjectFile,
4906        components: &[String],
4907        global: bool,
4908        lexical_scope: &[String],
4909    ) -> LexicalTypeResolution {
4910        self.resolve_type_components_lexically_inner(
4911            analyzer,
4912            file,
4913            components,
4914            global,
4915            lexical_scope,
4916            TypeCandidateResolution::Canonical,
4917        )
4918    }
4919
4920    pub fn resolve_type_components_lexically_for_forward(
4921        &self,
4922        analyzer: &CppGraphSource<'_>,
4923        file: &ProjectFile,
4924        components: &[String],
4925        global: bool,
4926        lexical_scope: &[String],
4927    ) -> LexicalTypeResolution {
4928        self.resolve_type_components_lexically_inner(
4929            analyzer,
4930            file,
4931            components,
4932            global,
4933            lexical_scope,
4934            TypeCandidateResolution::PreserveAlias,
4935        )
4936    }
4937
4938    pub fn resolve_type_components_lexically_for_target(
4939        &self,
4940        analyzer: &CppGraphSource<'_>,
4941        file: &ProjectFile,
4942        components: &[String],
4943        global: bool,
4944        lexical_scope: &[String],
4945        target: &CodeUnit,
4946    ) -> LexicalTypeResolution {
4947        #[cfg(any(test, feature = "test-support"))]
4948        self.target_preserving_type_resolution_count
4949            .fetch_add(1, Ordering::Relaxed);
4950        self.resolve_type_components_lexically_inner(
4951            analyzer,
4952            file,
4953            components,
4954            global,
4955            lexical_scope,
4956            TypeCandidateResolution::PreserveTarget(target),
4957        )
4958    }
4959
4960    pub fn coarse_unqualified_type_reference_may_resolve(
4961        &self,
4962        file: &ProjectFile,
4963        name: &str,
4964    ) -> bool {
4965        if name.is_empty() {
4966            return true;
4967        }
4968        self.visible_identifier_candidates(file, name)
4969            .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
4970            || self.visible_parser_alias_name_is_visible(file, name)
4971    }
4972
4973    #[allow(clippy::too_many_arguments)]
4974    pub fn structured_type_reference_may_resolve_to_target(
4975        &self,
4976        analyzer: &CppGraphSource<'_>,
4977        file: &ProjectFile,
4978        components: &[String],
4979        global: bool,
4980        lexical_scope: &[String],
4981        target: &CodeUnit,
4982    ) -> bool {
4983        if components.is_empty() {
4984            return true;
4985        }
4986        let Some(terminal) = components.last() else {
4987            return true;
4988        };
4989        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
4990            .map(|qualified| qualified.join("::"))
4991            .collect::<Vec<_>>();
4992        let target_name = cpp_name_for(target);
4993        if qualified_tiers
4994            .iter()
4995            .any(|qualified| qualified == &target_name)
4996        {
4997            return true;
4998        }
4999
5000        let mut saw_shape_candidate = false;
5001        for candidate in self.visible_identifier_candidates(file, terminal) {
5002            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
5003            {
5004                continue;
5005            }
5006            let candidate_name = cpp_name_for(candidate);
5007            let shape_matches = if global || components.len() > 1 {
5008                qualified_tiers
5009                    .iter()
5010                    .any(|qualified| qualified == &candidate_name)
5011            } else {
5012                true
5013            };
5014            if !shape_matches {
5015                continue;
5016            }
5017            saw_shape_candidate = true;
5018            if same_visible_symbol(candidate, target)
5019                || self.compatible_primary_template_redeclarations(candidate, target)
5020                || (declared_type_alias(analyzer, candidate)
5021                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
5022            {
5023                return true;
5024            }
5025        }
5026
5027        !saw_shape_candidate
5028    }
5029
5030    pub fn target_preserving_reference_namespace(
5031        &self,
5032        analyzer: &CppGraphSource<'_>,
5033        file: &ProjectFile,
5034        identifier: &str,
5035        target: &CodeUnit,
5036    ) -> Option<Vec<String>> {
5037        let mut namespace = None;
5038        for candidate in self.visible_identifier_candidates(file, identifier) {
5039            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
5040            {
5041                continue;
5042            }
5043            if !(same_visible_symbol(candidate, target)
5044                || self.compatible_primary_template_redeclarations(candidate, target)
5045                || declared_type_alias(analyzer, candidate)
5046                    && self.structured_alias_primary_preserves_target(
5047                        analyzer, file, candidate, target,
5048                    ))
5049            {
5050                continue;
5051            }
5052            if namespace
5053                .as_ref()
5054                .is_some_and(|existing| existing != candidate.package_name())
5055            {
5056                return None;
5057            }
5058            namespace = Some(candidate.package_name().to_string());
5059        }
5060        let namespace = namespace?;
5061        Some(
5062            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5063                brokk_bifrost_core::analyzer::Language::Cpp,
5064                &namespace,
5065            ),
5066        )
5067    }
5068
5069    pub fn resolve_imported_type_candidate(
5070        &self,
5071        analyzer: &CppGraphSource<'_>,
5072        file: &ProjectFile,
5073        target: &CodeUnit,
5074        target_components: &[String],
5075        direct_target: Option<&CodeUnit>,
5076        preserve_alias: bool,
5077    ) -> LexicalTypeResolution {
5078        let candidates = [target];
5079        let resolution = if preserve_alias {
5080            TypeCandidateResolution::PreserveAlias
5081        } else {
5082            direct_target.map_or(
5083                TypeCandidateResolution::Canonical,
5084                TypeCandidateResolution::PreserveTarget,
5085            )
5086        };
5087        // One candidate goes in, so a failure here is never "choose one of
5088        // these": it is the alias chain leaving the index, which must answer
5089        // missing rather than ambiguous (#1828).
5090        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5091            Ok(unit) => LexicalTypeResolution::Resolved {
5092                unit,
5093                components: target_components.to_vec(),
5094                candidates: vec![target.clone()],
5095            },
5096            Err(failure) => failure.lexical_resolution(),
5097        }
5098    }
5099
5100    fn resolve_type_components_lexically_inner(
5101        &self,
5102        analyzer: &CppGraphSource<'_>,
5103        file: &ProjectFile,
5104        components: &[String],
5105        global: bool,
5106        lexical_scope: &[String],
5107        resolution: TypeCandidateResolution<'_>,
5108    ) -> LexicalTypeResolution {
5109        if components.is_empty() {
5110            return LexicalTypeResolution::Missing;
5111        }
5112        // A C++ class injects its own name into the class scope.  The indexed
5113        // FqName for that declaration is the class path itself (for example,
5114        // `n::raw_hash_set`), not a synthetic child named
5115        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
5116        // requested identifier to every scope component, so they cannot
5117        // represent that injected binding when the enclosing class is the
5118        // closest scope.  Recover the binding from the structured class path
5119        // before allowing lookup to fall through to an outer same-spelled
5120        // declaration.
5121        let mut injected = self.resolve_injected_class_name(
5122            analyzer,
5123            file,
5124            components,
5125            global,
5126            lexical_scope,
5127            resolution,
5128        );
5129        for qualified in lexical_component_tiers(components, global, lexical_scope) {
5130            let prefix_len = qualified.len().saturating_sub(components.len());
5131            if injected
5132                .as_ref()
5133                .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
5134            {
5135                return injected
5136                    .take()
5137                    .expect("injected class resolution was just present")
5138                    .1;
5139            }
5140            let qualified_name = qualified.join("::");
5141            let candidates = self
5142                .type_candidates(file, &qualified_name)
5143                .into_iter()
5144                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
5145                .collect::<Vec<_>>();
5146            if candidates.is_empty() {
5147                if !global && components.len() == 1 {
5148                    match self.resolve_inherited_type_for_lexical_scope(
5149                        analyzer,
5150                        file,
5151                        &qualified[..prefix_len],
5152                        &components[0],
5153                        resolution,
5154                    ) {
5155                        LexicalTypeResolution::Missing => {}
5156                        inherited => return inherited,
5157                    }
5158                }
5159                continue;
5160            }
5161            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5162                Ok(unit) => unit,
5163                Err(failure) => return failure.lexical_resolution(),
5164            };
5165            return LexicalTypeResolution::Resolved {
5166                unit,
5167                components: qualified,
5168                candidates: candidates.into_iter().cloned().collect(),
5169            };
5170        }
5171        LexicalTypeResolution::Missing
5172    }
5173
5174    fn resolve_injected_class_name(
5175        &self,
5176        analyzer: &CppGraphSource<'_>,
5177        file: &ProjectFile,
5178        components: &[String],
5179        global: bool,
5180        lexical_scope: &[String],
5181        resolution: TypeCandidateResolution<'_>,
5182    ) -> Option<(usize, LexicalTypeResolution)> {
5183        if global
5184            || components.len() != 1
5185            || file.rel_path().extension().is_some_and(|ext| ext == "c")
5186            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
5187        {
5188            return None;
5189        }
5190        let name = components.first()?;
5191        let mut matches: Vec<&CodeUnit> = Vec::new();
5192        let mut owner_len = 0;
5193        for candidate in self.visible_identifier_candidates(file, name) {
5194            if !candidate.is_class()
5195                || declared_type_alias(analyzer, candidate)
5196                || candidate.identifier() != name
5197            {
5198                continue;
5199            }
5200            let candidate_scope = canonical_cpp_scope_components(candidate);
5201            if candidate_scope.len() > lexical_scope.len()
5202                || !lexical_scope.starts_with(&candidate_scope)
5203                || candidate_scope.last().is_none_or(|last| last != name)
5204            {
5205                continue;
5206            }
5207            if candidate_scope.len() > owner_len {
5208                owner_len = candidate_scope.len();
5209                matches.clear();
5210            }
5211            if candidate_scope.len() == owner_len
5212                && !matches
5213                    .iter()
5214                    .any(|existing| same_logical_symbol(existing, candidate))
5215            {
5216                matches.push(candidate);
5217            }
5218        }
5219        if matches.is_empty() {
5220            return None;
5221        }
5222        // A same-named class at the current lexical boundary is already
5223        // represented by the ordinary namespace/class tier.  The injected
5224        // recovery is only needed when lookup is occurring inside a nested
5225        // class, where the enclosing class name is injected across that
5226        // additional class boundary.  Keeping this boundary strict avoids
5227        // treating qualified receiver/static-qualifier context as an
5228        // injected-name reference.
5229        if owner_len >= lexical_scope.len() {
5230            return None;
5231        }
5232        let owner_components = lexical_scope[..owner_len].to_vec();
5233        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
5234            Ok(unit) => LexicalTypeResolution::Resolved {
5235                unit,
5236                components: owner_components,
5237                candidates: matches.into_iter().cloned().collect(),
5238            },
5239            Err(failure) => failure.lexical_resolution(),
5240        };
5241        Some((owner_len, resolution))
5242    }
5243
5244    fn resolve_inherited_type_for_lexical_scope(
5245        &self,
5246        analyzer: &CppGraphSource<'_>,
5247        file: &ProjectFile,
5248        lexical_scope: &[String],
5249        name: &str,
5250        resolution: TypeCandidateResolution<'_>,
5251    ) -> LexicalTypeResolution {
5252        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
5253            return LexicalTypeResolution::Missing;
5254        };
5255        let lexical_owner_name = lexical_scope.join("::");
5256        if lexical_owner_name.is_empty() {
5257            return LexicalTypeResolution::Missing;
5258        }
5259        let owner_candidates = self
5260            .type_candidates(file, &lexical_owner_name)
5261            .into_iter()
5262            .filter(|candidate| {
5263                canonical_cpp_name_matches(candidate, &lexical_owner_name)
5264                    && !declared_type_alias(analyzer, candidate)
5265            })
5266            .collect::<Vec<_>>();
5267        if owner_candidates.is_empty() {
5268            return LexicalTypeResolution::Missing;
5269        }
5270        // A visible forward declaration and the physical class definition share
5271        // one FQN, but only the definition owns hierarchy facts. When lookup is
5272        // physically inside that definition, do not let an earlier header
5273        // forward declaration erase its base edges (#2240).
5274        let physical_owner_candidates = owner_candidates
5275            .iter()
5276            .copied()
5277            .filter(|candidate| candidate.source() == file)
5278            .collect::<Vec<_>>();
5279        let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
5280            owner_candidates
5281        } else {
5282            physical_owner_candidates
5283        };
5284        let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
5285            return LexicalTypeResolution::Ambiguous;
5286        };
5287
5288        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
5289        let mut visited_owners = HashSet::default();
5290        while !frontier.is_empty() {
5291            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
5292            let mut next_frontier = Vec::new();
5293            for owner in frontier {
5294                if !visited_owners.insert(owner.fq_name()) {
5295                    continue;
5296                }
5297                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
5298                let candidates = self
5299                    .type_candidates(file, &qualified_name)
5300                    .into_iter()
5301                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
5302                    .collect::<Vec<_>>();
5303                if candidates.is_empty() {
5304                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
5305                        if !next_frontier
5306                            .iter()
5307                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
5308                        {
5309                            next_frontier.push(ancestor);
5310                        }
5311                    }
5312                    continue;
5313                }
5314                let unit =
5315                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
5316                        Ok(unit) => unit,
5317                        Err(failure) => return failure.lexical_resolution(),
5318                    };
5319                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
5320            }
5321            if let Some((unit, candidates)) = level_matches.first().cloned() {
5322                let Some(first_declaration) = candidates.first() else {
5323                    return LexicalTypeResolution::Ambiguous;
5324                };
5325                if !level_matches.iter().all(|(_, declarations)| {
5326                    declarations
5327                        .iter()
5328                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
5329                }) {
5330                    return LexicalTypeResolution::Ambiguous;
5331                }
5332                let mut components = lexical_scope.to_vec();
5333                components.push(name.to_string());
5334                return LexicalTypeResolution::Resolved {
5335                    unit,
5336                    components,
5337                    candidates,
5338                };
5339            }
5340            frontier = next_frontier;
5341        }
5342        LexicalTypeResolution::Missing
5343    }
5344
5345    /// Resolve a base class through its injected class name at the nearest
5346    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
5347    ///
5348    /// A base whose canonical full definition cannot be pinned from `file` -
5349    /// a forward declaration the include closure completes with two different
5350    /// full definitions, or an alias chain that leaves the index - stops the
5351    /// walk only when that base is spelled `injected_name`. The mem-initializer
5352    /// names a base by that base's own injected class name, so a base spelled
5353    /// differently can never be the one it names, whichever definition it would
5354    /// have turned out to be; aborting the level on its account instead loses
5355    /// the sibling base that *is* named (#2543). A base that is spelled
5356    /// `injected_name` still fails closed, because choosing a deeper same-named
5357    /// ancestor over it would bind the initializer to the wrong constructor.
5358    /// The skipped base carries its own ancestors out of the walk with it: with
5359    /// no canonical unit, the repeated-base accounting below cannot tell one
5360    /// inherited path through it from two.
5361    pub fn inherited_injected_class_owner(
5362        &self,
5363        analyzer: &CppGraphSource<'_>,
5364        file: &ProjectFile,
5365        enclosing_owner: &CodeUnit,
5366        injected_name: &str,
5367    ) -> Option<CodeUnit> {
5368        let hierarchy = analyzer.type_hierarchy_provider()?;
5369        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
5370        let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
5371        while !frontier.is_empty() {
5372            let mut level_matches = Vec::new();
5373            let mut next_frontier = Vec::new();
5374            for raw_owner in frontier {
5375                let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
5376                else {
5377                    if raw_owner.identifier() == injected_name {
5378                        return None;
5379                    }
5380                    continue;
5381                };
5382                let propagated = propagated_counts.entry(owner.clone()).or_default();
5383                if *propagated == 2 {
5384                    continue;
5385                }
5386                *propagated += 1;
5387                if owner.identifier() == injected_name {
5388                    level_matches.push(owner.clone());
5389                }
5390                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
5391            }
5392            match level_matches.as_slice() {
5393                [owner] => return Some(owner.clone()),
5394                [_, ..] => return None,
5395                [] => {}
5396            }
5397            frontier = next_frontier;
5398        }
5399        None
5400    }
5401
5402    /// The one type the candidates name under `resolution`, or why they do not
5403    /// name one. The two preserving modes only ever reject candidates that
5404    /// disagree with each other, which is ambiguity; canonicalization can also
5405    /// fail because the alias chain leaves the index (#1828).
5406    fn resolve_type_candidates(
5407        &self,
5408        analyzer: &CppGraphSource<'_>,
5409        file: &ProjectFile,
5410        candidates: &[&CodeUnit],
5411        resolution: TypeCandidateResolution<'_>,
5412    ) -> Result<CodeUnit, TypeCandidateFailure> {
5413        match resolution {
5414            TypeCandidateResolution::Canonical => {
5415                self.canonical_type_candidate_resolution(analyzer, file, candidates)
5416            }
5417            TypeCandidateResolution::PreserveAlias => {
5418                // A generated index can retain identical alias spellings from
5419                // mutually exclusive headers. When the reference file
5420                // physically reaches exactly one of those source declarations,
5421                // include closure is the structured evidence that selects it;
5422                // treating the two source spellings as an overload set makes a
5423                // reachable alias appear ambiguous (#1844).
5424                let same_fqn_alias_family = candidates.len() > 1
5425                    && candidates.iter().all(|candidate| {
5426                        declared_type_alias(analyzer, candidate)
5427                            && same_logical_symbol(candidates[0], candidate)
5428                    })
5429                    && candidates
5430                        .iter()
5431                        .any(|candidate| candidate.source() != candidates[0].source());
5432                if same_fqn_alias_family {
5433                    let physically_visible = candidates
5434                        .iter()
5435                        .copied()
5436                        .filter(|candidate| self.is_physically_visible(file, candidate))
5437                        .collect::<Vec<_>>();
5438                    // The family is one logical declaration only when the
5439                    // reachable spellings agree. Two same-FQN aliases whose
5440                    // written targets differ (`using Choice = Canonical;` in
5441                    // one header, `using Choice = ::Canonical;` in another)
5442                    // are a genuine conflict, and choosing the first indexed
5443                    // one silently binds the reference to an arbitrary owner
5444                    // (#2398). Collapse only a single reachable declaration
5445                    // or reachable declarations with one structured target;
5446                    // everything else stays ambiguous below.
5447                    let one_structured_target = physically_visible.len() > 1
5448                        && physically_visible.iter().skip(1).all(|candidate| {
5449                            let target = self.structured_alias_target(analyzer, candidate);
5450                            target.is_some()
5451                                && target
5452                                    == self.structured_alias_target(analyzer, physically_visible[0])
5453                        });
5454                    if physically_visible.len() == 1 || one_structured_target {
5455                        return Ok(physically_visible[0].clone());
5456                    }
5457                }
5458                unique_type_candidate_preserving_alias(analyzer, candidates)
5459                    .ok_or(TypeCandidateFailure::Ambiguous)
5460            }
5461            TypeCandidateResolution::PreserveTarget(target) => self
5462                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
5463                .ok_or(TypeCandidateFailure::Ambiguous),
5464        }
5465    }
5466
5467    pub fn resolve_callable_value_components_lexically(
5468        &self,
5469        analyzer: &CppGraphSource<'_>,
5470        file: &ProjectFile,
5471        owner_components: &[String],
5472        member_name: &str,
5473        global: bool,
5474        lexical_scope: &[String],
5475    ) -> LexicalCallableValueResolution {
5476        if owner_components.is_empty() || member_name.is_empty() {
5477            return LexicalCallableValueResolution::Missing;
5478        }
5479        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
5480            let owner_name = qualified_owner.join("::");
5481            let type_candidates = self
5482                .type_candidates(file, &owner_name)
5483                .into_iter()
5484                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
5485                .collect::<Vec<_>>();
5486            let resolved_type = if type_candidates.is_empty() {
5487                None
5488            } else {
5489                let Some(unit) =
5490                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
5491                else {
5492                    return LexicalCallableValueResolution::Ambiguous;
5493                };
5494                Some(unit)
5495            };
5496
5497            let mut qualified_callable = qualified_owner;
5498            qualified_callable.push(member_name.to_string());
5499            let callable_name = qualified_callable.join("::");
5500            let free_function = self
5501                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
5502                .into_iter()
5503                .find(|candidate| {
5504                    canonical_cpp_name_matches(candidate, &callable_name)
5505                        && type_owner_of(analyzer, candidate).is_none()
5506                })
5507                .cloned();
5508
5509            match (resolved_type, free_function) {
5510                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
5511                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
5512                (None, Some(function)) => {
5513                    return LexicalCallableValueResolution::FreeFunction(function);
5514                }
5515                (None, None) => {}
5516            }
5517        }
5518        LexicalCallableValueResolution::Missing
5519    }
5520
5521    fn resolve_type_for_declaration(
5522        &self,
5523        visible_from: &ProjectFile,
5524        declaration: &CodeUnit,
5525        raw_name: &str,
5526    ) -> Option<CodeUnit> {
5527        let normalized = normalize_reference_name(raw_name)?;
5528        if !normalized.contains("::")
5529            && let Some(namespace) = cpp_namespace_for(declaration)
5530        {
5531            for prefix in namespace_prefixes(&namespace) {
5532                let qualified = format!("{prefix}::{normalized}");
5533                if let Some(unit) = self
5534                    .type_candidates(visible_from, &qualified)
5535                    .into_iter()
5536                    .next()
5537                {
5538                    return Some(unit.clone());
5539                }
5540            }
5541        }
5542        self.resolve_type(visible_from, raw_name)
5543    }
5544
5545    fn resolve_unique_canonical_type_for_declaration(
5546        &self,
5547        analyzer: &CppGraphSource<'_>,
5548        visible_from: &ProjectFile,
5549        declaration: &CodeUnit,
5550        raw_name: &str,
5551    ) -> Option<CodeUnit> {
5552        let mut current =
5553            self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
5554        let mut seen_aliases = HashSet::default();
5555        loop {
5556            let Some(target) = self.structured_alias_target(analyzer, &current) else {
5557                return current.is_class().then_some(current);
5558            };
5559            if matches!(target, StructuredAliasTarget::Builtin) {
5560                return current.is_class().then_some(current);
5561            }
5562            if !seen_aliases.insert(current.clone()) {
5563                return None;
5564            }
5565            current = self.resolve_structured_alias_target(visible_from, &current, &target)?;
5566        }
5567    }
5568
5569    pub fn canonical_type_unit(
5570        &self,
5571        analyzer: &CppGraphSource<'_>,
5572        visible_from: &ProjectFile,
5573        unit: &CodeUnit,
5574    ) -> Option<CodeUnit> {
5575        self.canonical_type_resolution(analyzer, visible_from, unit)
5576            .ok()
5577    }
5578
5579    /// Follow `unit`'s alias chain to the class it names, or report why the
5580    /// chain does not end at one indexed class.
5581    ///
5582    /// A chain that leaves the index - an alias to a template parameter, to a
5583    /// standard-library type, or to any other declaration the workspace does
5584    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
5585    /// there is still nothing to choose between.
5586    fn canonical_type_resolution(
5587        &self,
5588        analyzer: &CppGraphSource<'_>,
5589        visible_from: &ProjectFile,
5590        unit: &CodeUnit,
5591    ) -> Result<CodeUnit, TypeCandidateFailure> {
5592        let mut current = unit.clone();
5593        let mut seen_aliases = HashSet::default();
5594        loop {
5595            let Some(target) = self.structured_alias_target(analyzer, &current) else {
5596                return current
5597                    .is_class()
5598                    .then_some(current)
5599                    .ok_or(TypeCandidateFailure::Unresolvable);
5600            };
5601            if matches!(target, StructuredAliasTarget::Builtin) {
5602                return current
5603                    .is_class()
5604                    .then_some(current)
5605                    .ok_or(TypeCandidateFailure::Unresolvable);
5606            }
5607            if !seen_aliases.insert(current.clone()) {
5608                return Err(TypeCandidateFailure::Unresolvable);
5609            }
5610            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
5611        }
5612    }
5613
5614    pub fn canonical_visible_full_type_unit(
5615        &self,
5616        analyzer: &CppGraphSource<'_>,
5617        visible_from: &ProjectFile,
5618        unit: &CodeUnit,
5619    ) -> Option<CodeUnit> {
5620        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
5621        if cpp_class_declaration_strength(analyzer, &canonical)
5622            != CppClassDeclarationStrength::Forward
5623        {
5624            return Some(canonical);
5625        }
5626        let mut full = Vec::new();
5627        for candidate in self
5628            .visible_identifier_candidates(visible_from, canonical.identifier())
5629            .filter(|candidate| {
5630                candidate.is_class()
5631                    && candidate.fq_name() == canonical.fq_name()
5632                    && cpp_class_declaration_strength(analyzer, candidate)
5633                        == CppClassDeclarationStrength::Full
5634            })
5635        {
5636            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
5637                full.push(candidate.clone());
5638            }
5639        }
5640        match full.len() {
5641            0 => Some(canonical),
5642            1 => full.pop(),
5643            _ => None,
5644        }
5645    }
5646
5647    fn resolve_structured_alias_target(
5648        &self,
5649        visible_from: &ProjectFile,
5650        declaration: &CodeUnit,
5651        target: &StructuredAliasTarget,
5652    ) -> Option<CodeUnit> {
5653        self.structured_alias_target_resolution(visible_from, declaration, target)
5654            .ok()
5655    }
5656
5657    fn structured_alias_target_resolution(
5658        &self,
5659        visible_from: &ProjectFile,
5660        declaration: &CodeUnit,
5661        target: &StructuredAliasTarget,
5662    ) -> Result<CodeUnit, TypeCandidateFailure> {
5663        let primary =
5664            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
5665        let StructuredAliasTarget::Named { arguments, .. } = target else {
5666            return Err(TypeCandidateFailure::Unresolvable);
5667        };
5668        match arguments {
5669            Some(arguments) => self
5670                .resolve_template_arguments(visible_from, primary, arguments)
5671                .map_err(|error| match error {
5672                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
5673                        TypeCandidateFailure::Ambiguous
5674                    }
5675                    _ => TypeCandidateFailure::Unresolvable,
5676                }),
5677            None => Ok(primary),
5678        }
5679    }
5680
5681    fn resolve_structured_alias_primary(
5682        &self,
5683        visible_from: &ProjectFile,
5684        declaration: &CodeUnit,
5685        target: &StructuredAliasTarget,
5686    ) -> Option<CodeUnit> {
5687        self.structured_alias_primary_resolution(visible_from, declaration, target)
5688            .ok()
5689    }
5690
5691    fn structured_alias_primary_resolution(
5692        &self,
5693        visible_from: &ProjectFile,
5694        declaration: &CodeUnit,
5695        target: &StructuredAliasTarget,
5696    ) -> Result<CodeUnit, TypeCandidateFailure> {
5697        let StructuredAliasTarget::Named {
5698            components, global, ..
5699        } = target
5700        else {
5701            return Err(TypeCandidateFailure::Unresolvable);
5702        };
5703        let qualified = components.join("::");
5704        let candidates = if *global {
5705            // `::A::B` anchors at the root scope, so a candidate whose
5706            // canonical path merely ends with the spelled components does not
5707            // qualify. Without this filter a global `::Canonical` target also
5708            // collects `alpha::Canonical`, the lookup reports a false
5709            // ambiguity, and the alias arm silently drops out of its
5710            // conflicting family instead of proving the conflict (#2398).
5711            let mut candidates = self.type_candidates(visible_from, &qualified);
5712            candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
5713            candidates
5714        } else {
5715            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
5716        };
5717        logical_type_candidate(candidates)
5718    }
5719
5720    pub fn structured_alias_primary_preserves_target(
5721        &self,
5722        analyzer: &CppGraphSource<'_>,
5723        visible_from: &ProjectFile,
5724        candidate: &CodeUnit,
5725        target: &CodeUnit,
5726    ) -> bool {
5727        let mut current = candidate.clone();
5728        let mut seen = HashSet::default();
5729        let mut matched_target = false;
5730        loop {
5731            if same_visible_symbol(&current, target)
5732                || self.compatible_primary_template_redeclarations(&current, target)
5733            {
5734                matched_target = true;
5735            }
5736            if !seen.insert(current.clone()) {
5737                return false;
5738            }
5739            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
5740                return matched_target;
5741            };
5742            if matches!(alias_target, StructuredAliasTarget::Builtin) {
5743                return matched_target;
5744            };
5745            let Some(primary) =
5746                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
5747            else {
5748                // A dependent member target such as `Detector<T>::type`
5749                // cannot be reduced to an indexed primary, but a preceding
5750                // structured alias hop may already have proven the requested
5751                // alias identity. Cycles still resolve a primary and are
5752                // rejected by `seen` above.
5753                return matched_target;
5754            };
5755            current = primary;
5756        }
5757    }
5758
5759    pub fn structured_class_alias_resolves_to_target(
5760        &self,
5761        analyzer: &CppGraphSource<'_>,
5762        visible_from: &ProjectFile,
5763        alias: &CodeUnit,
5764        target: &CodeUnit,
5765    ) -> bool {
5766        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
5767            return false;
5768        };
5769        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
5770            return false;
5771        };
5772        let StructuredAliasTarget::Named {
5773            components, global, ..
5774        } = &alias_target
5775        else {
5776            return false;
5777        };
5778        let lexical_scope = canonical_cpp_scope_components(&owner);
5779        match self.resolve_type_components_lexically_for_target(
5780            analyzer,
5781            visible_from,
5782            components,
5783            *global,
5784            &lexical_scope,
5785            target,
5786        ) {
5787            LexicalTypeResolution::Resolved {
5788                unit, candidates, ..
5789            } => {
5790                same_visible_symbol(&unit, target)
5791                    || self.same_template_member_identity(analyzer, &unit, target)
5792                    || candidates.iter().any(|candidate| {
5793                        same_visible_symbol(candidate, target)
5794                            || self.same_template_member_identity(analyzer, candidate, target)
5795                    })
5796            }
5797            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
5798                self.structured_alias_primary_preserves_target(
5799                    analyzer,
5800                    visible_from,
5801                    alias,
5802                    target,
5803                ) || self.flattened_macro_namespace_alias_target_matches(
5804                    analyzer,
5805                    visible_from,
5806                    alias,
5807                    &alias_target,
5808                    target,
5809                )
5810            }
5811        }
5812    }
5813
5814    /// Return true when a class-owned alias names the requested type as one
5815    /// structured qualifier in its target path.
5816    ///
5817    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
5818    /// indexed class. Forward lookup can still retain `Primary` as its bounded
5819    /// canonical identity. Inverse lookup needs the same evidence when later
5820    /// references use only the alias spelling.
5821    pub fn structured_class_alias_path_preserves_target(
5822        &self,
5823        analyzer: &CppGraphSource<'_>,
5824        visible_from: &ProjectFile,
5825        alias: &CodeUnit,
5826        target: &CodeUnit,
5827    ) -> bool {
5828        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
5829            return false;
5830        };
5831        let Some(StructuredAliasTarget::Named {
5832            components, global, ..
5833        }) = self.structured_alias_target(analyzer, alias)
5834        else {
5835            return false;
5836        };
5837        let lexical_scope = canonical_cpp_scope_components(&owner);
5838        (1..components.len()).rev().any(|component_count| {
5839            matches!(
5840                self.resolve_type_components_lexically_for_target(
5841                    analyzer,
5842                    visible_from,
5843                    &components[..component_count],
5844                    global,
5845                    &lexical_scope,
5846                    target,
5847                ),
5848                LexicalTypeResolution::Resolved {
5849                    ref unit,
5850                    ref candidates,
5851                    ..
5852                } if same_visible_symbol(unit, target)
5853                    || self.same_template_member_identity(analyzer, unit, target)
5854                    || candidates.iter().any(|candidate| {
5855                        same_visible_symbol(candidate, target)
5856                            || self.same_template_member_identity(analyzer, candidate, target)
5857                    })
5858            )
5859        })
5860    }
5861
5862    fn flattened_macro_namespace_alias_target_matches(
5863        &self,
5864        analyzer: &CppGraphSource<'_>,
5865        visible_from: &ProjectFile,
5866        alias: &CodeUnit,
5867        alias_target: &StructuredAliasTarget,
5868        target: &CodeUnit,
5869    ) -> bool {
5870        let StructuredAliasTarget::Named {
5871            components,
5872            global: false,
5873            arguments: None,
5874        } = alias_target
5875        else {
5876            return false;
5877        };
5878        let Some((target_name, namespace_components)) = components.split_last() else {
5879            return false;
5880        };
5881        if namespace_components.is_empty()
5882            || target_name != target.identifier()
5883            || alias.source() != target.source()
5884            || alias.source() != visible_from
5885            || !target.is_class()
5886            || declared_type_alias(analyzer, target)
5887        {
5888            return false;
5889        }
5890        if self
5891            .resolve_structured_alias_target(visible_from, alias, alias_target)
5892            .is_some()
5893        {
5894            return false;
5895        }
5896
5897        let alias_ranges = analyzer.ranges(alias);
5898        let target_ranges = analyzer.ranges(target);
5899        if alias_ranges.is_empty() || target_ranges.is_empty() {
5900            return false;
5901        }
5902        let alias_start = alias_ranges
5903            .iter()
5904            .map(|range| range.start_byte)
5905            .min()
5906            .expect("non-empty alias ranges have a minimum");
5907        let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
5908            return false;
5909        };
5910        let root = prepared.tree().root_node();
5911        let has_matching_declaration = target_ranges
5912            .iter()
5913            .filter(|range| range.end_byte <= alias_start)
5914            .filter_map(|range| node_for_exact_range(root, range))
5915            .any(|node| {
5916                flattened_macro_namespace_components(node, prepared.source())
5917                    .is_some_and(|recovered| recovered == namespace_components)
5918            });
5919        if !has_matching_declaration {
5920            return false;
5921        }
5922
5923        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
5924        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
5925        guard_requirement_sets_match(&alias_guards, &target_guards)
5926    }
5927
5928    pub fn template_alias_arguments_preserve_target(
5929        &self,
5930        analyzer: &CppGraphSource<'_>,
5931        visible_from: &ProjectFile,
5932        alias: &CodeUnit,
5933        arguments: &[CppTemplateExpression],
5934        target: &CodeUnit,
5935    ) -> bool {
5936        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
5937            return false;
5938        };
5939        if metadata.alias_target.is_none()
5940            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
5941        {
5942            return false;
5943        }
5944        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
5945    }
5946
5947    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
5948        self.cpp_template_metadata
5949            .get(unit)
5950            .is_some_and(CppTemplateMetadata::is_primary)
5951    }
5952
5953    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
5954        self.cpp_template_metadata
5955            .get(unit)
5956            .is_some_and(CppTemplateMetadata::is_specialization)
5957    }
5958
5959    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
5960        same_visible_symbol(left, right)
5961            || self.compatible_primary_template_redeclarations(left, right)
5962    }
5963
5964    pub fn same_template_member_identity(
5965        &self,
5966        analyzer: &CppGraphSource<'_>,
5967        left: &CodeUnit,
5968        right: &CodeUnit,
5969    ) -> bool {
5970        if same_visible_symbol(left, right) {
5971            return true;
5972        }
5973        if left.kind() != right.kind()
5974            || left.identifier() != right.identifier()
5975            || left.signature() != right.signature()
5976        {
5977            return false;
5978        }
5979        let (Some(left_owner), Some(right_owner)) =
5980            (analyzer.parent_of(left), analyzer.parent_of(right))
5981        else {
5982            return false;
5983        };
5984        left_owner.is_class()
5985            && right_owner.is_class()
5986            && self.same_template_owner_identity(&left_owner, &right_owner)
5987    }
5988
5989    fn unique_canonical_type_candidate(
5990        &self,
5991        analyzer: &CppGraphSource<'_>,
5992        visible_from: &ProjectFile,
5993        candidates: &[&CodeUnit],
5994    ) -> Option<CodeUnit> {
5995        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
5996            .ok()
5997    }
5998
5999    fn canonical_type_candidate_resolution(
6000        &self,
6001        analyzer: &CppGraphSource<'_>,
6002        visible_from: &ProjectFile,
6003        candidates: &[&CodeUnit],
6004    ) -> Result<CodeUnit, TypeCandidateFailure> {
6005        let mut canonical = Vec::new();
6006        for candidate in candidates {
6007            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
6008            if canonical
6009                .iter()
6010                .any(|existing| same_visible_symbol(existing, &resolved))
6011            {
6012                continue;
6013            }
6014            if let Some(existing) = canonical.iter_mut().find(|existing| {
6015                self.compatible_primary_template_redeclarations(existing, &resolved)
6016            }) {
6017                // A forward declaration and its full primary-template
6018                // definition are one C++ type even when they live in
6019                // different headers and alpha-rename their parameters. The
6020                // target-preserving path already reconciles this family; do
6021                // the same for ordinary canonical lookup so an out-of-line
6022                // member's lexical owner is not made ambiguous by its own
6023                // forward declaration. Retain the strongest physical
6024                // declaration for later owner/range queries.
6025                if matches!(
6026                    (
6027                        cpp_class_declaration_strength(analyzer, existing),
6028                        cpp_class_declaration_strength(analyzer, &resolved),
6029                    ),
6030                    (
6031                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
6032                        CppClassDeclarationStrength::Full,
6033                    ) | (
6034                        CppClassDeclarationStrength::Unknown,
6035                        CppClassDeclarationStrength::Forward,
6036                    )
6037                ) {
6038                    *existing = resolved;
6039                }
6040                continue;
6041            }
6042            canonical.push(resolved);
6043            if canonical.len() > 1 {
6044                return Err(TypeCandidateFailure::Ambiguous);
6045            }
6046        }
6047        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
6048    }
6049
6050    pub fn unique_type_candidate_preserving_target(
6051        &self,
6052        analyzer: &CppGraphSource<'_>,
6053        visible_from: &ProjectFile,
6054        candidates: &[&CodeUnit],
6055        target: &CodeUnit,
6056    ) -> Option<CodeUnit> {
6057        // C++ headers often expose one logical type through mutually exclusive
6058        // physical declarations, for example a class in the fallback branch
6059        // and a `using` alias to the standard-library type in the configured
6060        // branch. The index intentionally retains both declarations so forward
6061        // lookup can report each target. Preserve the requested target when
6062        // that is the only ambiguity: every candidate has the same type kind,
6063        // exact canonical FQN, and source file, and the requested declaration
6064        // itself is one of the physical candidates. Do not merge same-named
6065        // declarations from different files or namespaces; those remain
6066        // ambiguous and fail closed below.
6067        if self.alternate_same_fqn_type_declarations(analyzer, candidates, target) {
6068            return Some(target.clone());
6069        }
6070        let mut resolved_candidates = Vec::new();
6071        for candidate in candidates {
6072            // An ifdef branch that aliases an unindexed system type (for
6073            // example `typedef pthread_mutex_t k5_os_mutex`) cannot be
6074            // canonicalized. That branch does not name `target`. Dropping it
6075            // keeps the branch that does. Failing the whole family here would
6076            // deny every usage of the reachable spelling (#2368).
6077            let Some(resolved) =
6078                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
6079            else {
6080                continue;
6081            };
6082            if resolved_candidates
6083                .iter()
6084                .any(|existing| same_visible_symbol(existing, &resolved))
6085            {
6086                continue;
6087            }
6088            resolved_candidates.push(resolved);
6089        }
6090        match resolved_candidates.as_slice() {
6091            [] => None,
6092            [single] => Some(single.clone()),
6093            // The branches disagree about what the name aliases. When they are
6094            // spellings of one entity (#1845) that disagreement is a build
6095            // configuration, not a choice between types, so it must not deny
6096            // the requested target its reference.
6097            _ => self
6098                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
6099                .map(|_| target.clone()),
6100        }
6101    }
6102
6103    /// The declaration a same-file same-FQN family stands for when a reference
6104    /// names `target`, or `None` when the candidates are not one family or the
6105    /// family does not name `target`.
6106    ///
6107    /// A translation unit cannot hold two different types under one qualified
6108    /// name, so several same-kind declarations of one FQN in one file are
6109    /// alternate spellings of one entity - the configuration branches of an
6110    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
6111    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
6112    /// targets differ; canonicalizing each branch on its own and then demanding
6113    /// agreement reports an ambiguity that denies every declaration in the
6114    /// family its usages (#1845). The family names `target` when it declares
6115    /// it, or when one branch's alias chain reaches it.
6116    ///
6117    /// Declarations in different files or namespaces are distinct entities and
6118    /// are deliberately excluded: their disagreement is a real ambiguity.
6119    pub fn same_fqn_type_spelling_for_target<'b>(
6120        &self,
6121        analyzer: &CppGraphSource<'_>,
6122        visible_from: &ProjectFile,
6123        candidates: &[&'b CodeUnit],
6124        target: &CodeUnit,
6125    ) -> Option<&'b CodeUnit> {
6126        let [first, rest @ ..] = candidates else {
6127            return None;
6128        };
6129        if rest.is_empty()
6130            || !rest.iter().all(|candidate| {
6131                candidate.kind() == first.kind()
6132                    && candidate.fq_name() == first.fq_name()
6133                    && candidate.source() == first.source()
6134            })
6135        {
6136            return None;
6137        }
6138        candidates
6139            .iter()
6140            .copied()
6141            .find(|candidate| same_symbol(candidate, target))
6142            .or_else(|| {
6143                candidates.iter().copied().find(|candidate| {
6144                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
6145                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
6146                })
6147            })
6148    }
6149
6150    pub fn alternate_same_fqn_type_declarations(
6151        &self,
6152        analyzer: &CppGraphSource<'_>,
6153        candidates: &[&CodeUnit],
6154        target: &CodeUnit,
6155    ) -> bool {
6156        let Some(first) = candidates.first() else {
6157            return false;
6158        };
6159        let same_api = first.kind() == target.kind()
6160            && first.fq_name() == target.fq_name()
6161            && first.source() == target.source()
6162            && candidates.iter().all(|candidate| {
6163                candidate.kind() == target.kind()
6164                    && candidate.fq_name() == target.fq_name()
6165                    && candidate.source() == target.source()
6166            })
6167            && candidates
6168                .iter()
6169                .any(|candidate| same_symbol(candidate, target))
6170            && candidates
6171                .iter()
6172                .any(|candidate| !same_logical_symbol(candidate, target));
6173        if !same_api {
6174            return false;
6175        }
6176
6177        let requirements = candidates
6178            .iter()
6179            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
6180            .collect::<Vec<_>>();
6181        requirements.len() > 1
6182            && requirements
6183                .iter()
6184                .all(|requirement| !requirement.is_empty())
6185            && requirements.iter().enumerate().all(|(index, left)| {
6186                requirements[index + 1..].iter().all(|right| {
6187                    left.iter().all(|(_, left_guards)| {
6188                        right.iter().all(|(_, right_guards)| {
6189                            merge_preprocessor_guards(left_guards, right_guards).is_none()
6190                        })
6191                    })
6192                })
6193            })
6194    }
6195
6196    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
6197        let mut pending = vec![terms.to_vec()];
6198        while let Some(branch_terms) = pending.pop() {
6199            let mut normalized = Vec::new();
6200            let mut covers_branch = false;
6201            for term in branch_terms {
6202                if term.iter().any(|guard| term.contains(&guard.negated())) {
6203                    continue;
6204                }
6205                if term.is_empty() {
6206                    covers_branch = true;
6207                    break;
6208                }
6209                if !normalized.iter().any(|existing| existing == &term) {
6210                    normalized.push(term);
6211                }
6212            }
6213            if covers_branch {
6214                continue;
6215            }
6216            let Some(split_guard) = normalized
6217                .iter()
6218                .flat_map(|term| term.iter())
6219                .next()
6220                .cloned()
6221            else {
6222                return false;
6223            };
6224            let negated_guard = split_guard.negated();
6225            let mut when_defined = Vec::new();
6226            let mut when_undefined = Vec::new();
6227            for term in normalized {
6228                if term.contains(&negated_guard) {
6229                    // This term cannot hold when `split_guard` is true.
6230                } else if term.contains(&split_guard) {
6231                    let mut reduced = term.clone();
6232                    reduced.remove(&split_guard);
6233                    when_defined.push(reduced);
6234                } else {
6235                    when_defined.push(term.clone());
6236                }
6237                if term.contains(&split_guard) {
6238                    // This term cannot hold when `split_guard` is false.
6239                } else if term.contains(&negated_guard) {
6240                    let mut reduced = term;
6241                    reduced.remove(&negated_guard);
6242                    when_undefined.push(reduced);
6243                } else {
6244                    when_undefined.push(term);
6245                }
6246            }
6247            pending.push(when_defined);
6248            pending.push(when_undefined);
6249        }
6250        true
6251    }
6252
6253    /// The byte range of the one `#if` family with a terminal `#else` that holds
6254    /// every physical declaration of every candidate, or `None` when they do not
6255    /// share one such family.
6256    ///
6257    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
6258    /// whose macros changed between declarations. Require every physical range to
6259    /// belong to one syntax-tree family with a terminal `#else` before the terms
6260    /// can prove branch coverage.
6261    fn declarations_share_exhaustive_conditional_family(
6262        &self,
6263        analyzer: &CppGraphSource<'_>,
6264        candidates: &[&CodeUnit],
6265    ) -> Option<(usize, usize)> {
6266        let mut family_range = None;
6267        for candidate in candidates {
6268            let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
6269            let root = prepared.tree().root_node();
6270            let mut candidate_family = None;
6271            for range in analyzer.ranges(candidate) {
6272                let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
6273                let family = preprocessor_conditional_family_for_declaration(node)?;
6274                let key = (family.start_byte(), family.end_byte());
6275                if candidate_family.is_some_and(|existing| existing != key) {
6276                    return None;
6277                }
6278                candidate_family = Some(key);
6279            }
6280            let candidate_family = candidate_family?;
6281            if family_range.is_some_and(|existing| existing != candidate_family) {
6282                return None;
6283            }
6284            family_range = Some(candidate_family);
6285        }
6286        family_range
6287    }
6288
6289    pub fn complementary_same_fqn_type_declarations(
6290        &self,
6291        analyzer: &CppGraphSource<'_>,
6292        candidates: &[&CodeUnit],
6293        target: &CodeUnit,
6294    ) -> bool {
6295        if candidates.len() < 2
6296            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
6297            || self
6298                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
6299                .is_none()
6300        {
6301            return false;
6302        }
6303        Self::preprocessor_guard_terms_cover_all_paths(
6304            &self.declaration_family_guard_terms(analyzer, candidates),
6305        )
6306    }
6307
6308    fn declaration_family_guard_terms(
6309        &self,
6310        analyzer: &CppGraphSource<'_>,
6311        candidates: &[&CodeUnit],
6312    ) -> Vec<HashSet<PreprocessorGuard>> {
6313        candidates
6314            .iter()
6315            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
6316            .map(|(_, guards)| guards)
6317            .collect()
6318    }
6319
6320    /// A callable name declared on every branch of one completed `#if`/`#else`
6321    /// family is declared on every configuration path, so a reference below the
6322    /// whole family sees one of the branches whatever the preprocessor decides.
6323    /// Answer the family's end byte: only past `#endif` is every branch's
6324    /// declaration behind the reference.
6325    ///
6326    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
6327    /// and shares both of its primitives. It does not require two distinct
6328    /// `CodeUnit`s: branches that declare the same signature can collapse into
6329    /// one unit carrying one physical range per branch.
6330    ///
6331    /// The branches are alternate spellings of one declaration, never competing
6332    /// declarations, so only the first branch stands for the family. Reporting
6333    /// every branch as visible would turn a name the source declares exactly
6334    /// once into an ambiguity between build configurations.
6335    fn exhaustive_guard_family_activation(
6336        &self,
6337        analyzer: &CppGraphSource<'_>,
6338        prepared: &PreparedSyntaxTree,
6339        candidate: &CodeUnit,
6340        reference: &CallableReferenceContext<'_>,
6341    ) -> Option<usize> {
6342        // Branch coverage says nothing about scope: a block-local declaration
6343        // stays invisible however many branches declare it.
6344        if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
6345            return None;
6346        }
6347        let family = self
6348            .visible_identifier_candidates(candidate.source(), candidate.identifier())
6349            .filter(|peer| {
6350                peer.kind() == candidate.kind()
6351                    && peer.fq_name() == candidate.fq_name()
6352                    && peer.source() == candidate.source()
6353            })
6354            .collect::<Vec<_>>();
6355        let (_, family_end) =
6356            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
6357        if !Self::preprocessor_guard_terms_cover_all_paths(
6358            &self.declaration_family_guard_terms(analyzer, &family),
6359        ) {
6360            return None;
6361        }
6362        // A reference whose own guards pick one branch already reaches that
6363        // branch through the ordinary same-guard path; the family must not
6364        // resurrect the branch the reference contradicts.
6365        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
6366            .iter()
6367            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
6368        {
6369            return None;
6370        }
6371        (first_declaration_byte(analyzer, candidate)?
6372            == family
6373                .iter()
6374                .filter_map(|peer| first_declaration_byte(analyzer, peer))
6375                .min()?)
6376        .then_some(family_end)
6377    }
6378
6379    fn type_candidate_preserving_target(
6380        &self,
6381        analyzer: &CppGraphSource<'_>,
6382        visible_from: &ProjectFile,
6383        candidate: &CodeUnit,
6384        target: &CodeUnit,
6385    ) -> Option<CodeUnit> {
6386        let mut current = candidate.clone();
6387        let mut matched_target = same_visible_symbol(&current, target)
6388            || self.compatible_primary_template_redeclarations(&current, target);
6389        let mut seen = HashSet::default();
6390        loop {
6391            if !seen.insert(current.clone()) {
6392                return None;
6393            }
6394            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
6395                return matched_target
6396                    .then(|| target.clone())
6397                    .or_else(|| current.is_class().then_some(current));
6398            };
6399            if self.flattened_macro_namespace_alias_target_matches(
6400                analyzer,
6401                visible_from,
6402                &current,
6403                &alias_target,
6404                target,
6405            ) {
6406                return Some(target.clone());
6407            }
6408            if matches!(alias_target, StructuredAliasTarget::Builtin) {
6409                return matched_target
6410                    .then(|| target.clone())
6411                    .or_else(|| current.is_class().then_some(current));
6412            }
6413            // A non-template alias can name a template alias with explicit
6414            // arguments (for example, `using Result = Expected<int>`).  When
6415            // the requested target is that alias's primary declaration, keep
6416            // the primary identity before expanding the RHS arguments.  The
6417            // expansion would otherwise canonicalize through the underlying
6418            // implementation type and lose the target spelling used by the
6419            // forward resolver.
6420            if !self.cpp_template_metadata.contains_key(&current)
6421                && let Some(primary) =
6422                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
6423                && (same_visible_symbol(&primary, target)
6424                    || self.compatible_primary_template_redeclarations(&primary, target))
6425            {
6426                return Some(target.clone());
6427            }
6428            if same_visible_symbol(&current, target) {
6429                return Some(target.clone());
6430            }
6431            if self.cpp_template_metadata.contains_key(&current) {
6432                return None;
6433            }
6434            let Some(next) =
6435                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
6436            else {
6437                return matched_target.then(|| target.clone());
6438            };
6439            current = next;
6440            matched_target |= same_visible_symbol(&current, target)
6441                || self.compatible_primary_template_redeclarations(&current, target);
6442        }
6443    }
6444
6445    fn compatible_primary_template_redeclarations(
6446        &self,
6447        left: &CodeUnit,
6448        right: &CodeUnit,
6449    ) -> bool {
6450        let (Some(left_metadata), Some(right_metadata)) = (
6451            self.cpp_template_metadata.get(left),
6452            self.cpp_template_metadata.get(right),
6453        ) else {
6454            return false;
6455        };
6456        left_metadata.primary_fq_name == right_metadata.primary_fq_name
6457            && left_metadata.is_primary()
6458            && right_metadata.is_primary()
6459            && cpp_reconcile_primary_template_parameters(
6460                &[(left, left_metadata), (right, right_metadata)],
6461                right,
6462            )
6463            .is_some()
6464    }
6465
6466    fn alias_candidate_may_preserve_target(
6467        &self,
6468        analyzer: &CppGraphSource<'_>,
6469        visible_from: &ProjectFile,
6470        candidate: &CodeUnit,
6471        target: &CodeUnit,
6472    ) -> bool {
6473        let mut current = candidate.clone();
6474        let mut seen = HashSet::default();
6475        loop {
6476            if same_visible_symbol(&current, target)
6477                || self.compatible_primary_template_redeclarations(&current, target)
6478            {
6479                return true;
6480            }
6481            if self.cpp_template_metadata.contains_key(&current) {
6482                return true;
6483            }
6484            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
6485                return false;
6486            };
6487            let StructuredAliasTarget::Named {
6488                components,
6489                global,
6490                arguments,
6491            } = alias_target
6492            else {
6493                return false;
6494            };
6495            if arguments.is_some() || !seen.insert(current.clone()) {
6496                return true;
6497            }
6498            let qualified = components.join("::");
6499            let next = if global {
6500                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
6501            } else {
6502                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
6503            };
6504            let Some(next) = next else {
6505                return true;
6506            };
6507            current = next;
6508        }
6509    }
6510
6511    /// Every indexed type declaration `raw_name` names when it is written in
6512    /// `declaration`'s namespace: the innermost enclosing namespace that holds
6513    /// the name wins, otherwise the name is looked up unqualified.
6514    fn type_candidates_for_declaration<'b>(
6515        &'b self,
6516        visible_from: &ProjectFile,
6517        declaration: &CodeUnit,
6518        raw_name: &str,
6519    ) -> Vec<&'b CodeUnit> {
6520        let Some(normalized) = normalize_reference_name(raw_name) else {
6521            return Vec::new();
6522        };
6523        if let Some(namespace) = cpp_namespace_for(declaration) {
6524            for prefix in namespace_prefixes(&namespace) {
6525                let qualified = format!("{prefix}::{normalized}");
6526                let candidates = self.type_candidates(visible_from, &qualified);
6527                if !candidates.is_empty() {
6528                    return candidates;
6529                }
6530            }
6531        }
6532        self.type_candidates(visible_from, &normalized)
6533    }
6534
6535    fn resolve_unique_type_for_declaration(
6536        &self,
6537        visible_from: &ProjectFile,
6538        declaration: &CodeUnit,
6539        raw_name: &str,
6540    ) -> Option<CodeUnit> {
6541        unique_logical_type_candidate(self.type_candidates_for_declaration(
6542            visible_from,
6543            declaration,
6544            raw_name,
6545        ))
6546    }
6547
6548    pub fn resolves_to_type(
6549        &self,
6550        analyzer: &CppGraphSource<'_>,
6551        file: &ProjectFile,
6552        raw_name: &str,
6553        target: &CodeUnit,
6554    ) -> bool {
6555        let Some(normalized) = normalize_reference_name(raw_name) else {
6556            return false;
6557        };
6558        let candidates = self.type_candidates(file, &normalized);
6559        if candidates.is_empty() {
6560            return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
6561        }
6562        let Some(resolved) =
6563            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
6564        else {
6565            return false;
6566        };
6567        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
6568    }
6569
6570    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
6571        let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
6572        let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
6573        match resolved.kind() {
6574            CodeUnitType::Class => Some(resolved),
6575            _ if is_type_alias(&resolved) => self.alias_target(&resolved),
6576            _ => None,
6577        }
6578    }
6579
6580    /// Whether two callable declarations declare one function.
6581    ///
6582    /// [`same_logical_symbol`] compares the persisted signature strings, which
6583    /// embed each parameter type exactly as it was spelled. A header
6584    /// declaration written inside `namespace zmq { class dist_t { ... } }` says
6585    /// `send_to_matching(msg_t *)` while its out-of-line body at file scope
6586    /// says `zmq::msg_t *`, so the string comparison reports two symbols where
6587    /// C++ ([basic.def], [dcl.fct]) sees one declaration and one definition.
6588    /// This resolves the written parameter names before comparing them and
6589    /// reports the same answer the language does for the cases it can prove.
6590    ///
6591    /// Everything it cannot prove stays two symbols: a template declaration, a
6592    /// parameter with no comparable shape, a name that resolves on one side
6593    /// only, and an alias chain it cannot follow safely (#2010).
6594    pub fn same_logical_callable(
6595        &self,
6596        analyzer: &CppGraphSource<'_>,
6597        left: &CodeUnit,
6598        right: &CodeUnit,
6599    ) -> bool {
6600        if same_logical_symbol(left, right) {
6601            return true;
6602        }
6603        if left.kind() != right.kind()
6604            || !left.is_callable()
6605            || !right.is_callable()
6606            || left.fq_name() != right.fq_name()
6607        {
6608            return false;
6609        }
6610        // A template declaration and its out-of-line body can also diverge
6611        // outside the parameter list - `template <class T>` against
6612        // `template <typename T>` - and the template head is part of the
6613        // persisted signature. Deciding template-head equivalence is a
6614        // separate question, so templates keep string identity.
6615        if self.callable_is_template_declaration(analyzer, left)
6616            || self.callable_is_template_declaration(analyzer, right)
6617        {
6618            return false;
6619        }
6620        let (Some(left_comparable), Some(right_comparable)) = (
6621            self.callable_comparable(analyzer, left),
6622            self.callable_comparable(analyzer, right),
6623        ) else {
6624            return false;
6625        };
6626        // The trailing member `const`, ref-qualifier, `noexcept`, trailing
6627        // return type and requires-clause are part of C++ callable identity and
6628        // an out-of-line definition repeats them verbatim, so they must agree
6629        // as written.
6630        if left_comparable.suffix != right_comparable.suffix
6631            || left_comparable.shapes.len() != right_comparable.shapes.len()
6632        {
6633            return false;
6634        }
6635        left_comparable
6636            .shapes
6637            .iter()
6638            .zip(right_comparable.shapes.iter())
6639            .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
6640                (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
6641                (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
6642                    self.comparable_shapes_agree(analyzer, left_shape, right_shape)
6643                }
6644                // An unstructured parameter records that the reduction failed,
6645                // not that the two spellings mean the same type, so it agrees
6646                // with nothing - including another unstructured parameter.
6647                _ => false,
6648            })
6649    }
6650
6651    /// Compare two parameter shapes node by node with an explicit paired stack.
6652    ///
6653    /// Shape variants and cv-qualifiers must agree exactly at every level; only
6654    /// the named leaves may be spelled differently, and they agree when they
6655    /// resolve to one type declaration.
6656    fn comparable_shapes_agree(
6657        &self,
6658        analyzer: &CppGraphSource<'_>,
6659        left: &CppComparableParameter,
6660        right: &CppComparableParameter,
6661    ) -> bool {
6662        let mut stack = vec![(left.root(), right.root())];
6663        while let Some((left_index, right_index)) = stack.pop() {
6664            match (left.node(left_index), right.node(right_index)) {
6665                (
6666                    CppComparableNode::Named {
6667                        name: left_name,
6668                        primitive: left_primitive,
6669                        konst: left_konst,
6670                        volatil: left_volatil,
6671                    },
6672                    CppComparableNode::Named {
6673                        name: right_name,
6674                        primitive: right_primitive,
6675                        konst: right_konst,
6676                        volatil: right_volatil,
6677                    },
6678                ) => {
6679                    if left_konst != right_konst
6680                        || left_volatil != right_volatil
6681                        || left_primitive != right_primitive
6682                        || !self.comparable_names_agree(
6683                            analyzer,
6684                            left_name,
6685                            right_name,
6686                            *left_primitive,
6687                        )
6688                    {
6689                        return false;
6690                    }
6691                }
6692                (
6693                    CppComparableNode::Pointer {
6694                        inner: left_inner,
6695                        konst: left_konst,
6696                        volatil: left_volatil,
6697                    },
6698                    CppComparableNode::Pointer {
6699                        inner: right_inner,
6700                        konst: right_konst,
6701                        volatil: right_volatil,
6702                    },
6703                ) => {
6704                    if left_konst != right_konst || left_volatil != right_volatil {
6705                        return false;
6706                    }
6707                    stack.push((*left_inner, *right_inner));
6708                }
6709                (
6710                    CppComparableNode::Reference { inner: left_inner },
6711                    CppComparableNode::Reference { inner: right_inner },
6712                )
6713                | (
6714                    CppComparableNode::Array { inner: left_inner },
6715                    CppComparableNode::Array { inner: right_inner },
6716                ) => stack.push((*left_inner, *right_inner)),
6717                (
6718                    CppComparableNode::Generic {
6719                        base: left_base,
6720                        arguments: left_arguments,
6721                    },
6722                    CppComparableNode::Generic {
6723                        base: right_base,
6724                        arguments: right_arguments,
6725                    },
6726                ) => {
6727                    if left_arguments.len() != right_arguments.len() {
6728                        return false;
6729                    }
6730                    stack.push((*left_base, *right_base));
6731                    stack.extend(
6732                        left_arguments.iter().zip(right_arguments.iter()).map(
6733                            |(left_argument, right_argument)| (*left_argument, *right_argument),
6734                        ),
6735                    );
6736                }
6737                _ => return false,
6738            }
6739        }
6740        true
6741    }
6742
6743    /// Whether two written type names denote one type.
6744    ///
6745    /// A primitive denotes the same type in every scope, so its recorded
6746    /// lexical scope is noise and its spelling decides. A nominal name is
6747    /// resolved on each side independently: two resolved names agree when they
6748    /// reach one type declaration, and two unresolved names agree only on
6749    /// exact agreement of what was written, which is no weaker than the
6750    /// whole-signature string equality this comparison replaces. Resolution on
6751    /// one side only is evidence of difference, never of agreement.
6752    fn comparable_names_agree(
6753        &self,
6754        analyzer: &CppGraphSource<'_>,
6755        left: &StructuredTypeName,
6756        right: &StructuredTypeName,
6757        primitive: bool,
6758    ) -> bool {
6759        if primitive {
6760            return left.path() == right.path();
6761        }
6762        match (
6763            self.comparable_name_terminal(analyzer, left),
6764            self.comparable_name_terminal(analyzer, right),
6765        ) {
6766            (Some(left_terminal), Some(right_terminal)) => {
6767                same_logical_symbol(&left_terminal, &right_terminal)
6768            }
6769            (None, None) => {
6770                left.path() == right.path() && left.is_absolute() == right.is_absolute()
6771            }
6772            _ => false,
6773        }
6774    }
6775
6776    /// The class declaration a written type name denotes, or `None` when the
6777    /// workspace cannot prove one.
6778    ///
6779    /// The lookup is a closure-independent lexical-scope prefix walk over the
6780    /// workspace definition index rather than a visibility lookup: the index
6781    /// handed to a definition query is rooted at the reference file, and a
6782    /// body's `.cpp` is almost never in that file's include closure. Any name
6783    /// this walk resolves is one an enclosing-scope lookup could resolve, so it
6784    /// cannot invent a type the compiler could not see; `using`-directives are
6785    /// not modelled, and a name that needs one stays unresolved.
6786    fn comparable_name_terminal(
6787        &self,
6788        analyzer: &CppGraphSource<'_>,
6789        name: &StructuredTypeName,
6790    ) -> Option<CodeUnit> {
6791        let mut current = self.comparable_name_declaration(analyzer, name)?;
6792        let mut visited = HashSet::default();
6793        for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
6794            // The alias question is asked before the class question, and
6795            // through `declared_type_alias` rather than `is_type_alias`,
6796            // because extraction records `using A8 = A7;` as a *Class* unit
6797            // whose signature is the alias declaration. Reading the kind first
6798            // would end the chase on the alias itself and report an alias
6799            // spelling and its underlying class as two types (#2010).
6800            if !declared_type_alias(analyzer, &current) {
6801                return current.is_class().then_some(current);
6802            }
6803            if !visited.insert(current.clone()) {
6804                return None;
6805            }
6806            let signature = current.signature()?;
6807            // `cpp_alias_declaration_target_text` reads the declaration's
6808            // `type` field only, so `typedef Foo *Bar` reports `Foo` and the
6809            // pointer is silently dropped. Substituting such an alias would
6810            // fuse `f(Bar)` and `f(Foo)`, which are two functions.
6811            if cpp_alias_declaration_adds_indirection(signature) {
6812                return None;
6813            }
6814            let raw_target = cpp_alias_declaration_target_text(signature)?;
6815            current = self.comparable_alias_target(analyzer, &current, &raw_target)?;
6816        }
6817        None
6818    }
6819
6820    /// The declaration one alias hop lands on: the type `raw_target` names,
6821    /// looked up from the alias declaration's own enclosing namespace.
6822    ///
6823    /// The hop takes the same closure-independent prefix walk the first lookup
6824    /// took, and deliberately not `resolve_type_for_declaration`: that one
6825    /// answers out of the `VisibilityIndex`, which is rooted at the reference
6826    /// file, while the alias declaration this hop starts from is reached
6827    /// through the workspace definition index and its file need not be in that
6828    /// root's include closure - where the visibility lookup answers nothing and
6829    /// the chase would stop on the alias itself (#2010).
6830    fn comparable_alias_target(
6831        &self,
6832        analyzer: &CppGraphSource<'_>,
6833        alias: &CodeUnit,
6834        raw_target: &str,
6835    ) -> Option<CodeUnit> {
6836        // `raw_target` is the alias declaration's written type text, so it is a
6837        // plain `::`-joined qualified-id: the same domain the shared symbol-path
6838        // parser reads, and the same leading `::` that marks an absolute name
6839        // everywhere else this crate normalizes a reference.
6840        let absolute = raw_target.trim_start().starts_with("::");
6841        let normalized = normalize_reference_name(raw_target)?;
6842        let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6843            brokk_bifrost_core::analyzer::Language::Cpp,
6844            &normalized,
6845        );
6846        let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
6847            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6848                brokk_bifrost_core::analyzer::Language::Cpp,
6849                &namespace,
6850            )
6851        });
6852        let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
6853        self.comparable_name_declaration(analyzer, &name)
6854    }
6855
6856    /// The one type declaration `name` names, by enclosing scope, innermost
6857    /// first.
6858    ///
6859    /// The first prefix depth that names anything decides: an inner scope hides
6860    /// an outer one, so a match there is the answer even when an outer scope
6861    /// also declares the name. Several logically distinct declarations at that
6862    /// depth are an ambiguity this comparison must not guess at.
6863    fn comparable_name_declaration(
6864        &self,
6865        analyzer: &CppGraphSource<'_>,
6866        name: &StructuredTypeName,
6867    ) -> Option<CodeUnit> {
6868        let definitions = analyzer.workspace_definitions();
6869        let interner = segment_interner();
6870        let first_depth = if name.is_absolute() {
6871            0
6872        } else {
6873            name.lexical_scope().len()
6874        };
6875        for depth in (0..=first_depth).rev() {
6876            let mut structured = FqName::new();
6877            for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
6878                structured.push(interner.intern(component, SegmentKind::Unknown));
6879            }
6880            let mut candidates = definitions
6881                .identifier(&structured)
6882                .into_iter()
6883                .filter(|unit| unit.fq().same_segment_texts(&structured))
6884                .filter(|unit| {
6885                    unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
6886                });
6887            let Some(first) = candidates.next() else {
6888                continue;
6889            };
6890            return candidates
6891                .all(|unit| same_logical_symbol(&unit, &first))
6892                .then_some(first);
6893        }
6894        None
6895    }
6896
6897    /// The comparison inputs of one callable declaration, extracted once.
6898    ///
6899    /// The comparison itself runs only when two candidates share kind and fully
6900    /// qualified name but not signature, which is rare; re-reading the same
6901    /// declaration for every pair in a candidate set is not.
6902    fn callable_comparable(
6903        &self,
6904        analyzer: &CppGraphSource<'_>,
6905        unit: &CodeUnit,
6906    ) -> Option<Arc<ExtractedComparable>> {
6907        if let Some(cached) = self
6908            .callable_comparables
6909            .lock()
6910            .expect("C++ callable comparable cache poisoned")
6911            .get(unit)
6912            .cloned()
6913        {
6914            return cached;
6915        }
6916        let extracted = self
6917            .extract_callable_comparable(analyzer, unit)
6918            .map(Arc::new);
6919        self.callable_comparables
6920            .lock()
6921            .expect("C++ callable comparable cache poisoned")
6922            .insert(unit.clone(), extracted.clone());
6923        extracted
6924    }
6925
6926    fn extract_callable_comparable(
6927        &self,
6928        analyzer: &CppGraphSource<'_>,
6929        unit: &CodeUnit,
6930    ) -> Option<ExtractedComparable> {
6931        let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
6932        let root = prepared.tree().root_node();
6933        let declarator = analyzer
6934            .ranges(unit)
6935            .into_iter()
6936            .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
6937        Some(ExtractedComparable {
6938            // One question about one declarator: indexing the file's tree would
6939            // cost more than the walk it saves.
6940            shapes: cpp_comparable_parameter_shapes(
6941                declarator,
6942                prepared.source(),
6943                &ParentIndex::unindexed(),
6944            ),
6945            suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
6946        })
6947    }
6948
6949    pub fn canonical_type_for_reference(
6950        &self,
6951        file: &ProjectFile,
6952        raw_name: &str,
6953    ) -> Option<CodeUnit> {
6954        let resolved = self.resolve_type(file, raw_name)?;
6955        self.alias_target(&resolved).or(Some(resolved))
6956    }
6957
6958    pub fn parser_alias_resolves_to_type(
6959        &self,
6960        analyzer: &CppGraphSource<'_>,
6961        file: &ProjectFile,
6962        raw_name: &str,
6963        target: &CodeUnit,
6964    ) -> bool {
6965        let Some(alias_name) = normalize_reference_name(raw_name) else {
6966            return false;
6967        };
6968        let Some(cpp) = analyzer.cpp else {
6969            return false;
6970        };
6971        let matches_file = |source_file: &ProjectFile| {
6972            self.file_alias_matches(cpp, source_file, &alias_name, target)
6973        };
6974        self.visible_source_files_by_root.get(file).map_or_else(
6975            || matches_file(file),
6976            |files| files.iter().any(matches_file),
6977        )
6978    }
6979
6980    fn file_alias_matches(
6981        &self,
6982        cpp: &dyn CppSource,
6983        file: &ProjectFile,
6984        alias_name: &str,
6985        target: &CodeUnit,
6986    ) -> bool {
6987        let cell = {
6988            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
6989            Arc::clone(
6990                cells
6991                    .entry(file.clone())
6992                    .or_insert_with(|| Arc::new(OnceLock::new())),
6993            )
6994        };
6995        cell.get_or_init(|| {
6996            self.parser_alias_source_parses
6997                .fetch_add(1, Ordering::Relaxed);
6998            #[cfg(any(test, feature = "test-support"))]
6999            {
7000                *self
7001                    .alias_source_parse_counts
7002                    .lock()
7003                    .expect("alias source parse count lock")
7004                    .entry(file.clone())
7005                    .or_default() += 1;
7006            }
7007            aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
7008        })
7009        .iter()
7010        .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
7011    }
7012
7013    #[cfg(any(test, feature = "test-support"))]
7014    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
7015        self.visible_source_files_by_root
7016            .get(file)
7017            .cloned()
7018            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
7019    }
7020
7021    #[cfg(any(test, feature = "test-support"))]
7022    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
7023        self.alias_source_parse_counts
7024            .lock()
7025            .expect("alias source parse count lock")
7026            .get(file)
7027            .copied()
7028            .unwrap_or(0)
7029    }
7030
7031    pub fn resolve_named(
7032        &self,
7033        file: &ProjectFile,
7034        raw_name: &str,
7035        kind: TargetKind,
7036    ) -> Option<CodeUnit> {
7037        let normalized = normalize_reference_name(raw_name)?;
7038        self.named_candidates_for_normalized(file, &normalized, kind)
7039            .into_iter()
7040            .next()
7041            .cloned()
7042    }
7043
7044    pub fn contains_named_symbol(
7045        &self,
7046        file: &ProjectFile,
7047        raw_name: &str,
7048        kind: TargetKind,
7049        target: &CodeUnit,
7050    ) -> bool {
7051        let Some(normalized) = normalize_reference_name(raw_name) else {
7052            return false;
7053        };
7054        self.named_candidates_for_normalized(file, &normalized, kind)
7055            .into_iter()
7056            .any(|unit| {
7057                matches_kind_for_lookup(unit, kind)
7058                    && reference_matches_unit(&normalized, unit)
7059                    && same_visible_symbol(unit, target)
7060            })
7061    }
7062
7063    pub fn named_candidates(
7064        &self,
7065        file: &ProjectFile,
7066        raw_name: &str,
7067        kind: TargetKind,
7068    ) -> Vec<CodeUnit> {
7069        let Some(normalized) = normalize_reference_name(raw_name) else {
7070            return Vec::new();
7071        };
7072        self.named_candidates_for_normalized(file, &normalized, kind)
7073            .into_iter()
7074            .cloned()
7075            .collect()
7076    }
7077
7078    pub fn resolve_known_non_target(
7079        &self,
7080        file: &ProjectFile,
7081        raw_name: &str,
7082        kind: TargetKind,
7083        target: &CodeUnit,
7084    ) -> bool {
7085        let Some(normalized) = normalize_reference_name(raw_name) else {
7086            return false;
7087        };
7088        normalized.contains("::")
7089            && self
7090                .named_candidates_for_normalized(file, &normalized, kind)
7091                .into_iter()
7092                .any(|unit| {
7093                    matches_kind_for_lookup(unit, kind)
7094                        && reference_matches_unit(&normalized, unit)
7095                        && !same_visible_symbol(unit, target)
7096                })
7097    }
7098
7099    pub fn resolve_call_return_binding(
7100        &self,
7101        analyzer: &CppGraphSource<'_>,
7102        file: &ProjectFile,
7103        raw_name: &str,
7104        arity: usize,
7105        lexical_namespace: Option<&str>,
7106        direct_type: Option<&CodeUnit>,
7107    ) -> Option<CppScanBinding> {
7108        let normalized = normalize_reference_name(raw_name)?;
7109        let mut candidates = Vec::new();
7110        for function in
7111            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
7112        {
7113            if cpp_callable_arity(analyzer, function).accepts(arity)
7114                && !direct_type.is_some_and(|direct_type| {
7115                    self.callable_is_constructor_declaration(analyzer, function)
7116                        && type_owner_of(analyzer, function)
7117                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
7118                })
7119            {
7120                candidates.push(function.clone());
7121            }
7122        }
7123        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
7124        unanimous_return_binding(analyzer, self, file, &candidates)
7125    }
7126
7127    pub fn resolve_call_return_binding_without_arity(
7128        &self,
7129        analyzer: &CppGraphSource<'_>,
7130        file: &ProjectFile,
7131        raw_name: &str,
7132        lexical_namespace: Option<&str>,
7133        direct_type: Option<&CodeUnit>,
7134    ) -> (bool, Option<CppScanBinding>) {
7135        let Some(normalized) = normalize_reference_name(raw_name) else {
7136            return (false, None);
7137        };
7138        let mut candidates = self
7139            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
7140            .into_iter()
7141            .filter(|function| {
7142                function.is_function()
7143                    && !direct_type.is_some_and(|direct_type| {
7144                        self.callable_is_constructor_declaration(analyzer, function)
7145                            && type_owner_of(analyzer, function)
7146                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
7147                    })
7148            })
7149            .cloned()
7150            .collect::<Vec<_>>();
7151        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
7152        let has_candidates = !candidates.is_empty();
7153        (
7154            has_candidates,
7155            unanimous_return_binding(analyzer, self, file, &candidates),
7156        )
7157    }
7158
7159    pub fn visible_identifier_candidates<'b>(
7160        &'b self,
7161        file: &ProjectFile,
7162        identifier: &str,
7163    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
7164        self.visible_by_identifier
7165            .get(file)
7166            .and_then(|by_name| by_name.get(identifier))
7167            .into_iter()
7168            .flatten()
7169    }
7170
7171    /// Return terminal reference names that can denote `target` from `file`.
7172    ///
7173    /// The indexed candidate table covers ordinary declarations and aliases;
7174    /// Parser-only aliases are tested lazily when their spelling is actually
7175    /// encountered in a scanned type node. Enumerating them here would parse
7176    /// every source in the include closure even when the target's direct name
7177    /// is the only spelling present in the file.
7178    pub fn visible_type_reference_component_names_for_target(
7179        &self,
7180        analyzer: &CppGraphSource<'_>,
7181        file: &ProjectFile,
7182        target: &CodeUnit,
7183    ) -> HashSet<String> {
7184        let mut names = HashSet::from_iter([target.identifier().to_string()]);
7185        if let Some(metadata) = self.cpp_template_metadata.get(target) {
7186            names.insert(metadata.primary_name.clone());
7187        }
7188
7189        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
7190            for (identifier, candidates) in by_identifier {
7191                if candidates.iter().any(|candidate| {
7192                    (candidate.is_class()
7193                        && (same_visible_symbol(candidate, target)
7194                            || self.compatible_primary_template_redeclarations(candidate, target)))
7195                        || (declared_type_alias(analyzer, candidate)
7196                            && self.alias_candidate_may_preserve_target(
7197                                analyzer, file, candidate, target,
7198                            ))
7199                }) {
7200                    names.insert(identifier.clone());
7201                }
7202            }
7203        }
7204
7205        names
7206    }
7207
7208    pub fn indexed_structural_class_scope(
7209        &self,
7210        file: &ProjectFile,
7211        class: Node<'_>,
7212        source: &str,
7213    ) -> Option<Vec<String>> {
7214        let key = (file.clone(), class.start_byte(), class.end_byte());
7215        if let Some(cached) = self
7216            .indexed_structural_class_scopes
7217            .lock()
7218            .expect("C++ indexed structural-class scope cache poisoned")
7219            .get(&key)
7220            .cloned()
7221        {
7222            return cached;
7223        }
7224        let resolved = (|| {
7225            let name = class.child_by_field_name("name")?;
7226            let identifier = if name.kind() == "template_type" {
7227                node_text(name.child_by_field_name("name")?, source).to_string()
7228            } else {
7229                let mut components = Vec::new();
7230                append_cpp_name_components(name, source, &mut components)?;
7231                components.last()?.clone()
7232            };
7233            let visible = self
7234                .visible_identifier_candidates(file, &identifier)
7235                .cloned()
7236                .collect::<Vec<_>>();
7237            let mut visible = visible;
7238            for candidate in
7239                self.visible_by_file
7240                    .get(file)
7241                    .into_iter()
7242                    .flatten()
7243                    .filter(|candidate| {
7244                        self.cpp_template_metadata
7245                            .get(candidate)
7246                            .is_some_and(|metadata| metadata.primary_name == identifier)
7247                    })
7248            {
7249                if !visible
7250                    .iter()
7251                    .any(|existing| same_logical_symbol(existing, candidate))
7252                {
7253                    visible.push(candidate.clone());
7254                }
7255            }
7256            // Built once per call rather than per candidate; `cpp_source` rebuilds
7257            // the five-field source from the same `self.cpp` on every call.
7258            let cpp_source = self.cpp_source();
7259            let candidates = visible
7260                .iter()
7261                .filter(|candidate| {
7262                    candidate.source() == file
7263                        && candidate.is_class()
7264                        && !declared_type_alias(&cpp_source, candidate)
7265                        && self.cpp.ranges(candidate).iter().any(|range| {
7266                            range.start_byte <= class.start_byte()
7267                                && class.end_byte() <= range.end_byte
7268                        })
7269                })
7270                .collect::<Vec<_>>();
7271            let owner = if name.kind() == "template_type" {
7272                let expected = normalize_cpp_whitespace(node_text(name, source));
7273                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
7274                let exact = candidates
7275                    .iter()
7276                    .copied()
7277                    .filter(|candidate| {
7278                        candidate
7279                            .fq()
7280                            .segments()
7281                            .iter()
7282                            .rev()
7283                            .find_map(|&segment| {
7284                                let (text, kind) = interner.resolve(segment);
7285                                matches!(
7286                                    kind,
7287                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
7288                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
7289                                )
7290                                .then_some(text)
7291                            })
7292                            .is_some_and(|text| text == expected)
7293                    })
7294                    .collect::<Vec<_>>();
7295                unique_logical_type_candidate(exact)
7296                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
7297            } else {
7298                unique_logical_type_candidate(candidates)?
7299            };
7300            Some(canonical_cpp_scope_components(&owner))
7301        })();
7302        self.indexed_structural_class_scopes
7303            .lock()
7304            .expect("C++ indexed structural-class scope cache poisoned")
7305            .insert(key, resolved.clone());
7306        resolved
7307    }
7308
7309    pub fn indexed_enclosing_owner_scope(
7310        &self,
7311        analyzer: &CppGraphSource<'_>,
7312        file: &ProjectFile,
7313        node: Node<'_>,
7314    ) -> Option<Vec<String>> {
7315        let anchor = std::iter::successors(Some(node), |current| current.parent())
7316            .find(|current| {
7317                matches!(
7318                    current.kind(),
7319                    "function_definition"
7320                        | "class_specifier"
7321                        | "struct_specifier"
7322                        | "union_specifier"
7323                )
7324            })
7325            .unwrap_or(node);
7326        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
7327        if let Some(cached) = self
7328            .indexed_enclosing_owner_scopes
7329            .lock()
7330            .expect("C++ indexed enclosing-owner scope cache poisoned")
7331            .get(&key)
7332            .cloned()
7333        {
7334            return cached;
7335        }
7336        let resolved = (|| {
7337            let range = Range {
7338                start_byte: node.start_byte(),
7339                end_byte: node.end_byte(),
7340                start_line: node.start_position().row,
7341                end_line: node.end_position().row,
7342            };
7343            let start = analyzer.enclosing_code_unit(file, &range)?;
7344            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
7345                start,
7346                |unit| self.cached_precise_parent_of(analyzer, unit),
7347            )
7348            .find(|unit| {
7349                unit.is_class()
7350                    && !analyzer
7351                        .type_alias_provider()
7352                        .is_some_and(|provider| provider.is_type_alias(unit))
7353            })?;
7354            Some(canonical_cpp_scope_components(&owner))
7355        })();
7356        self.indexed_enclosing_owner_scopes
7357            .lock()
7358            .expect("C++ indexed enclosing-owner scope cache poisoned")
7359            .insert(key, resolved.clone());
7360        resolved
7361    }
7362
7363    fn cached_precise_parent_of(
7364        &self,
7365        analyzer: &CppGraphSource<'_>,
7366        code_unit: &CodeUnit,
7367    ) -> Option<CodeUnit> {
7368        if let Some(cached) = self
7369            .precise_parent_cache
7370            .lock()
7371            .expect("C++ precise-parent cache poisoned")
7372            .get(code_unit)
7373            .cloned()
7374        {
7375            return cached;
7376        }
7377        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
7378        self.precise_parent_cache
7379            .lock()
7380            .expect("C++ precise-parent cache poisoned")
7381            .insert(code_unit.clone(), resolved.clone());
7382        resolved
7383    }
7384
7385    pub fn callable_is_constructor_declaration(
7386        &self,
7387        analyzer: &CppGraphSource<'_>,
7388        candidate: &CodeUnit,
7389    ) -> bool {
7390        if !candidate.is_function() {
7391            return false;
7392        }
7393        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
7394            return false;
7395        };
7396        let root = prepared.tree().root_node();
7397        let candidate_ranges = analyzer.ranges(candidate);
7398        let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
7399            let mut current = root
7400                .descendant_for_byte_range(range.start_byte, range.end_byte)
7401                .and_then(|node| node.parent());
7402            while let Some(node) = current {
7403                if matches!(
7404                    node.kind(),
7405                    "class_specifier" | "struct_specifier" | "union_specifier"
7406                ) {
7407                    return node
7408                        .child_by_field_name("name")
7409                        .map(|name| terminal_name(node_text(name, prepared.source())))
7410                        .is_some_and(|name| name == candidate.identifier());
7411                }
7412                current = node.parent();
7413            }
7414            false
7415        });
7416        if enclosed_by_matching_type {
7417            return true;
7418        }
7419        let indexed_containment = analyzer
7420            .declarations(candidate.source())
7421            .into_iter()
7422            .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
7423            .any(|owner| {
7424                analyzer.ranges(&owner).iter().any(|owner_range| {
7425                    candidate_ranges.iter().any(|candidate_range| {
7426                        owner_range.start_byte <= candidate_range.start_byte
7427                            && candidate_range.end_byte <= owner_range.end_byte
7428                    })
7429                })
7430            });
7431        if indexed_containment {
7432            return true;
7433        }
7434        let metadata = analyzer.signature_metadata(candidate);
7435        !metadata.is_empty()
7436            && metadata
7437                .iter()
7438                .all(|signature| signature.return_type_text().is_none())
7439    }
7440
7441    /// Whether a callable declaration is a class-template deduction guide.
7442    ///
7443    /// Tree-sitter represents `Box(T) -> Box<T>;` as a declaration with no
7444    /// type field whose function declarator owns a trailing return type. This
7445    /// structured shape distinguishes a guide from both a constructor (no
7446    /// trailing return) and an ordinary trailing-return function (an `auto`
7447    /// type field).
7448    pub fn callable_is_deduction_guide_declaration(
7449        &self,
7450        analyzer: &CppGraphSource<'_>,
7451        candidate: &CodeUnit,
7452    ) -> bool {
7453        if !candidate.is_function() {
7454            return false;
7455        }
7456        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
7457            return false;
7458        };
7459        nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
7460            .into_iter()
7461            .any(|declaration| {
7462                if declaration.kind() != "declaration"
7463                    || declaration.child_by_field_name("type").is_some()
7464                {
7465                    return false;
7466                }
7467                let Some(declarator) = declaration.child_by_field_name("declarator") else {
7468                    return false;
7469                };
7470                if declarator.kind() != "function_declarator" {
7471                    return false;
7472                }
7473                let mut cursor = declarator.walk();
7474                let has_trailing_return = declarator
7475                    .named_children(&mut cursor)
7476                    .any(|child| child.kind() == "trailing_return_type");
7477                has_trailing_return
7478                    && declarator_name_node(declarator).is_some_and(|name| {
7479                        node_text(name, prepared.source()) == candidate.identifier()
7480                    })
7481            })
7482    }
7483
7484    /// Whether a callable occurrence is directly wrapped by a C++ template
7485    /// declaration. This deliberately inspects declaration syntax instead of
7486    /// inferring template status from the rendered signature.
7487    pub fn callable_is_template_declaration(
7488        &self,
7489        analyzer: &CppGraphSource<'_>,
7490        candidate: &CodeUnit,
7491    ) -> bool {
7492        if !candidate.is_function() {
7493            return false;
7494        }
7495        let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
7496            return false;
7497        };
7498        let root = prepared.tree().root_node();
7499        analyzer.ranges(candidate).iter().any(|range| {
7500            let Some(node) = node_for_exact_range(root, range)
7501                .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
7502            else {
7503                return false;
7504            };
7505            node.parent().is_some_and(|parent| {
7506                parent.kind() == "template_declaration"
7507                    && parent
7508                        .named_child(parent.named_child_count().saturating_sub(1))
7509                        .is_some_and(|declaration| same_node(declaration, node))
7510            })
7511        })
7512    }
7513
7514    pub fn type_name_candidates<'b>(
7515        &'b self,
7516        file: &ProjectFile,
7517        normalized: &str,
7518    ) -> Vec<&'b CodeUnit> {
7519        self.candidate_units(file, normalized, TargetKind::Type)
7520    }
7521
7522    pub fn visible_members_for_owner_name<'b>(
7523        &'b self,
7524        file: &ProjectFile,
7525        owner: &CodeUnit,
7526        name: &str,
7527    ) -> Vec<&'b CodeUnit> {
7528        self.visible_identifier_candidates(file, name)
7529            .filter(|unit| {
7530                // Structured owner pop on the unit's own `fq()` (shared with
7531                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
7532                // string.
7533                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
7534                    .is_some_and(|parent| parent == owner.fq_name())
7535            })
7536            .collect()
7537    }
7538
7539    pub fn visible_member_for_owner_name(
7540        &self,
7541        file: &ProjectFile,
7542        owner: &CodeUnit,
7543        name: &str,
7544    ) -> VisibleMemberResolution {
7545        let candidates = self.visible_members_for_owner_name(file, owner, name);
7546        let mut callables = Vec::new();
7547        let mut non_callable = None;
7548        for candidate in candidates {
7549            if candidate.is_function() {
7550                callables.push(candidate.clone());
7551            } else if non_callable.is_none() {
7552                non_callable = Some(candidate.clone());
7553            }
7554        }
7555        match (callables.is_empty(), non_callable) {
7556            (false, None) => VisibleMemberResolution::Callable(callables),
7557            (true, Some(_)) => VisibleMemberResolution::NonCallable,
7558            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
7559            (true, None) => VisibleMemberResolution::Missing,
7560        }
7561    }
7562
7563    fn field_declared_type_fact(
7564        &self,
7565        analyzer: &CppGraphSource<'_>,
7566        field: &CodeUnit,
7567    ) -> Option<DeclaredFieldTypeFact> {
7568        if let Some(cached) = self
7569            .field_type_facts
7570            .lock()
7571            .expect("C++ field type fact cache poisoned")
7572            .get(field)
7573            .cloned()
7574        {
7575            return cached;
7576        }
7577        let decoded = decode_field_declared_type_fact(analyzer, field);
7578        self.field_type_facts
7579            .lock()
7580            .expect("C++ field type fact cache poisoned")
7581            .insert(field.clone(), decoded.clone());
7582        decoded
7583    }
7584
7585    fn structured_alias_target(
7586        &self,
7587        analyzer: &CppGraphSource<'_>,
7588        unit: &CodeUnit,
7589    ) -> Option<StructuredAliasTarget> {
7590        if let Some(cached) = self
7591            .structured_alias_targets
7592            .lock()
7593            .expect("C++ structured alias target cache poisoned")
7594            .get(unit)
7595            .cloned()
7596        {
7597            return cached;
7598        }
7599        let decoded = decode_structured_alias_target(analyzer, unit);
7600        self.structured_alias_targets
7601            .lock()
7602            .expect("C++ structured alias target cache poisoned")
7603            .insert(unit.clone(), decoded.clone());
7604        decoded
7605    }
7606
7607    pub fn type_candidates<'b>(
7608        &'b self,
7609        file: &ProjectFile,
7610        normalized: &str,
7611    ) -> Vec<&'b CodeUnit> {
7612        let mut candidates = self
7613            .candidate_units(file, normalized, TargetKind::Type)
7614            .into_iter()
7615            .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
7616            .collect::<Vec<_>>();
7617        dedup_unit_refs(&mut candidates);
7618        candidates
7619    }
7620
7621    pub fn named_candidates_for_normalized<'b>(
7622        &'b self,
7623        file: &ProjectFile,
7624        normalized: &str,
7625        kind: TargetKind,
7626    ) -> Vec<&'b CodeUnit> {
7627        let mut candidates = self
7628            .candidate_units(file, normalized, kind)
7629            .into_iter()
7630            .filter(|unit| {
7631                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
7632            })
7633            .collect::<Vec<_>>();
7634        dedup_unit_refs(&mut candidates);
7635        candidates
7636    }
7637
7638    pub fn candidate_units<'b>(
7639        &'b self,
7640        file: &ProjectFile,
7641        normalized: &str,
7642        kind: TargetKind,
7643    ) -> Vec<&'b CodeUnit> {
7644        if normalized.contains("::") {
7645            // `normalized` comes from `normalize_cpp_reference_text`, which
7646            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
7647            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
7648            // kept intact by the shared splitter's operator merge — the same
7649            // domain `cpp_reference_fqn_candidates` below already parses with
7650            // the shared splitter. Re-tokenizing and taking the last segment
7651            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
7652            // scan exactly.
7653            let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7654                brokk_bifrost_core::analyzer::Language::Cpp,
7655                normalized,
7656            )
7657            .pop() else {
7658                return Vec::new();
7659            };
7660            let fqns = cpp_reference_fqn_candidates(normalized, kind);
7661            return self
7662                .visible_identifier_candidates(file, &identifier)
7663                .filter(|unit| {
7664                    #[cfg(any(test, feature = "test-support"))]
7665                    self.qualified_candidate_inspections
7666                        .fetch_add(1, Ordering::Relaxed);
7667                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
7668                        || canonical_cpp_name_matches(unit, normalized)
7669                })
7670                .collect();
7671        }
7672        self.visible_identifier_candidates(file, normalized)
7673            .collect()
7674    }
7675
7676    #[cfg(any(test, feature = "test-support"))]
7677    pub fn reset_qualified_candidate_inspections(&self) {
7678        self.qualified_candidate_inspections
7679            .store(0, Ordering::Relaxed);
7680    }
7681
7682    #[cfg(any(test, feature = "test-support"))]
7683    pub fn qualified_candidate_inspections(&self) -> usize {
7684        self.qualified_candidate_inspections.load(Ordering::Relaxed)
7685    }
7686
7687    #[cfg(any(test, feature = "test-support"))]
7688    pub fn reset_target_preserving_type_resolution_count(&self) {
7689        self.target_preserving_type_resolution_count
7690            .store(0, Ordering::Relaxed);
7691    }
7692
7693    #[cfg(any(test, feature = "test-support"))]
7694    pub fn target_preserving_type_resolution_count(&self) -> usize {
7695        self.target_preserving_type_resolution_count
7696            .load(Ordering::Relaxed)
7697    }
7698
7699    #[cfg(any(test, feature = "test-support"))]
7700    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
7701        self.visible_parser_alias_name_set_build_count
7702            .load(Ordering::Relaxed)
7703    }
7704}
7705
7706#[derive(Default)]
7707struct IncludeGraph {
7708    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
7709}
7710
7711impl IncludeGraph {
7712    fn extend_with<F>(
7713        &mut self,
7714        root: &ProjectFile,
7715        cancellation: Option<&CancellationToken>,
7716        targets_for: &mut F,
7717    ) where
7718        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
7719    {
7720        let mut stack = vec![root.clone()];
7721        while let Some(file) = stack.pop() {
7722            if cancellation.is_some_and(CancellationToken::is_cancelled) {
7723                break;
7724            }
7725            if self.targets_by_file.contains_key(&file) {
7726                continue;
7727            }
7728            let targets = targets_for(&file);
7729            stack.extend(targets.iter().cloned());
7730            self.targets_by_file.insert(file, targets);
7731        }
7732    }
7733
7734    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
7735        self.targets_by_file.keys()
7736    }
7737
7738    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
7739        self.targets_by_file
7740            .get(file)
7741            .map(Vec::as_slice)
7742            .unwrap_or_default()
7743    }
7744
7745    fn reachable_files(
7746        &self,
7747        root: &ProjectFile,
7748        cancellation: Option<&CancellationToken>,
7749    ) -> HashSet<ProjectFile> {
7750        let mut pending = vec![root.clone()];
7751        let mut visited = HashSet::default();
7752        while let Some(file) = pending.pop() {
7753            if cancellation.is_some_and(CancellationToken::is_cancelled) {
7754                break;
7755            }
7756            if visited.insert(file.clone()) {
7757                pending.extend(self.targets(&file).iter().cloned());
7758            }
7759        }
7760        visited
7761    }
7762}
7763
7764fn build_bounded_visible_declarations(
7765    cpp: &dyn CppSource,
7766    token: QueryToken<'_>,
7767    analyzer: &CppGraphSource<'_>,
7768    roots: &HashSet<ProjectFile>,
7769    visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
7770    cancellation: Option<&CancellationToken>,
7771    stats: &mut BoundedVisibilityStats,
7772) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
7773    roots
7774        .iter()
7775        .map(|root| {
7776            let reading_is_c = analyzer.reference_uses_c_semantics(root);
7777            let declarations_started = Instant::now();
7778            let root_declarations =
7779                bounded_visibility_declarations_in_reading(analyzer, root, reading_is_c);
7780            stats.declaration_elapsed += declarations_started.elapsed();
7781            stats.declaration_reads += 1;
7782            stats.declaration_units += root_declarations.len();
7783            let mut visible = root_declarations.into_iter().collect::<HashSet<_>>();
7784            let mut pending_names = HashSet::default();
7785            if let Some(prepared) = cpp.prepared_syntax(token, root) {
7786                let mut pending_nodes = vec![prepared.tree().root_node()];
7787                while let Some(node) = pending_nodes.pop() {
7788                    if matches!(
7789                        node.kind(),
7790                        "identifier"
7791                            | "type_identifier"
7792                            | "field_identifier"
7793                            | "namespace_identifier"
7794                    ) {
7795                        pending_names.insert(node_text(node, prepared.source()).to_string());
7796                    }
7797                    if node.kind() == "preproc_arg" {
7798                        for reference in
7799                            object_macro_replacement_type_references(node, prepared.source())
7800                        {
7801                            pending_names.extend(reference.components);
7802                        }
7803                    }
7804                    for index in 0..node.named_child_count() {
7805                        if let Some(child) = node.named_child(index) {
7806                            pending_nodes.push(child);
7807                        }
7808                    }
7809                }
7810            }
7811            stats.root_names += pending_names.len();
7812            let mut completed_names = HashSet::default();
7813            while !pending_names.is_empty() {
7814                stats.rounds += 1;
7815                let round_names = std::mem::take(&mut pending_names);
7816                let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
7817                    HashMap::default();
7818                for identifier in round_names {
7819                    if !completed_names.insert(identifier.clone())
7820                        || cancellation.is_some_and(CancellationToken::is_cancelled)
7821                    {
7822                        continue;
7823                    }
7824                    let lookup_started = Instant::now();
7825                    let candidates = cpp.visibility_identifier_candidates(&identifier);
7826                    stats.lookup_elapsed += lookup_started.elapsed();
7827                    stats.identifier_lookups += 1;
7828                    stats.candidate_units += candidates.len();
7829                    for source in candidates
7830                        .into_iter()
7831                        .map(|unit| unit.source().clone())
7832                        .collect::<HashSet<_>>()
7833                    {
7834                        if source != *root
7835                            && visible_sources
7836                                .get(root)
7837                                .is_some_and(|files| files.contains(&source))
7838                        {
7839                            requested_names_by_source
7840                                .entry(source)
7841                                .or_default()
7842                                .insert(identifier.clone());
7843                        }
7844                    }
7845                }
7846                stats.candidate_sources += requested_names_by_source.len();
7847                for (source, requested_names) in requested_names_by_source {
7848                    let declarations_started = Instant::now();
7849                    let declarations =
7850                        bounded_visibility_declarations_in_reading(analyzer, &source, reading_is_c);
7851                    stats.declaration_elapsed += declarations_started.elapsed();
7852                    stats.declaration_reads += 1;
7853                    stats.declaration_units += declarations.len();
7854                    for unit in declarations {
7855                        let template_metadata = unit
7856                            .is_class()
7857                            .then(|| cpp.template_metadata(&unit))
7858                            .flatten();
7859                        if !requested_names.contains(unit.identifier())
7860                            && !template_metadata.as_ref().is_some_and(|metadata| {
7861                                requested_names.contains(&metadata.primary_name)
7862                            })
7863                        {
7864                            continue;
7865                        }
7866                        stats.selected_units += 1;
7867                        if let Some(prepared) = cpp.prepared_syntax(token, &source) {
7868                            let ast_started = Instant::now();
7869                            for range in analyzer.ranges(&unit) {
7870                                let Some(declaration) =
7871                                    node_for_exact_range(prepared.tree().root_node(), &range)
7872                                else {
7873                                    continue;
7874                                };
7875                                let mut pending_nodes = vec![declaration];
7876                                while let Some(node) = pending_nodes.pop() {
7877                                    stats.dependency_ast_nodes += 1;
7878                                    if matches!(
7879                                        node.kind(),
7880                                        "type_identifier" | "namespace_identifier"
7881                                    ) {
7882                                        let name = node_text(node, prepared.source());
7883                                        if !completed_names.contains(name)
7884                                            && pending_names.insert(name.to_string())
7885                                        {
7886                                            stats.dependency_names += 1;
7887                                        }
7888                                    }
7889                                    for index in 0..node.named_child_count() {
7890                                        if let Some(child) = node.named_child(index) {
7891                                            pending_nodes.push(child);
7892                                        }
7893                                    }
7894                                }
7895                            }
7896                            stats.dependency_ast_elapsed += ast_started.elapsed();
7897                        }
7898                        if let Some(metadata) = template_metadata
7899                            && !completed_names.contains(&metadata.primary_name)
7900                        {
7901                            pending_names.insert(metadata.primary_name);
7902                        }
7903                        visible.insert(unit);
7904                    }
7905                }
7906            }
7907            (root.clone(), visible)
7908        })
7909        .collect()
7910}
7911
7912#[derive(Default)]
7913struct BoundedVisibilityStats {
7914    rounds: usize,
7915    root_names: usize,
7916    identifier_lookups: usize,
7917    candidate_units: usize,
7918    candidate_sources: usize,
7919    declaration_reads: usize,
7920    declaration_units: usize,
7921    selected_units: usize,
7922    dependency_ast_nodes: usize,
7923    dependency_names: usize,
7924    lookup_elapsed: Duration,
7925    declaration_elapsed: Duration,
7926    dependency_ast_elapsed: Duration,
7927}
7928
7929fn bounded_visibility_declarations_in_reading(
7930    analyzer: &CppGraphSource<'_>,
7931    file: &ProjectFile,
7932    c_semantics: bool,
7933) -> BTreeSet<CodeUnit> {
7934    #[cfg(any(test, feature = "test-support"))]
7935    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
7936    analyzer.declarations_in_reading(file, c_semantics)
7937}
7938
7939#[cfg(any(test, feature = "test-support"))]
7940pub fn reset_bounded_visibility_declaration_read_count_for_test() {
7941    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
7942}
7943
7944#[cfg(any(test, feature = "test-support"))]
7945pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
7946    BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
7947}
7948
7949pub struct VisibilityData {
7950    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
7951    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
7952}
7953
7954/// Build the per-root include closure and the declarations each root can see
7955/// through it.
7956///
7957/// `declarations_for` takes the reading to answer in (issue #1970): a root
7958/// compiled as C sees the C reading of every file in its closure, a root
7959/// compiled as C++ sees the C++ reading, and `reading_is_c_for` decides which
7960/// per root. The two readings agree for all but a handful of headers, so the
7961/// C map is built only when some root actually asks for it, and only over the
7962/// files that root reaches.
7963pub fn build_visibility_data<F, R, D>(
7964    roots: &HashSet<ProjectFile>,
7965    cancellation: Option<&CancellationToken>,
7966    mut targets_for: F,
7967    mut reading_is_c_for: R,
7968    mut declarations_for: D,
7969) -> VisibilityData
7970where
7971    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
7972    R: FnMut(&ProjectFile) -> bool,
7973    D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
7974{
7975    let mut include_graph = IncludeGraph::default();
7976    for file in roots {
7977        if cancellation.is_some_and(CancellationToken::is_cancelled) {
7978            break;
7979        }
7980        include_graph.extend_with(file, cancellation, &mut targets_for);
7981    }
7982    let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
7983        .files()
7984        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
7985        .map(|file| (file.clone(), declarations_for(file, false)))
7986        .collect();
7987    let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
7988    let mut visible_by_file = HashMap::default();
7989    let mut visible_source_files_by_root = HashMap::default();
7990    for file in roots {
7991        if cancellation.is_some_and(CancellationToken::is_cancelled) {
7992            break;
7993        }
7994        let mut visited = HashSet::default();
7995        let mut visible = HashSet::default();
7996        let declarations_by_file = if reading_is_c_for(file) {
7997            for reached in cpp_declarations_by_file.keys() {
7998                if !c_declarations_by_file.contains_key(reached) {
7999                    let declarations = declarations_for(reached, true);
8000                    c_declarations_by_file.insert(reached.clone(), declarations);
8001                }
8002            }
8003            &c_declarations_by_file
8004        } else {
8005            &cpp_declarations_by_file
8006        };
8007        collect_visible_declarations(
8008            &include_graph,
8009            declarations_by_file,
8010            file,
8011            &mut visited,
8012            &mut visible,
8013            cancellation,
8014        );
8015        visible_by_file.insert(file.clone(), visible);
8016        visible_source_files_by_root.insert(file.clone(), visited);
8017    }
8018    VisibilityData {
8019        visible_by_file,
8020        visible_source_files_by_root,
8021    }
8022}
8023
8024/// Admit the class that an out-of-line definition proves is in scope.
8025///
8026/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
8027/// names a class-like entity in that file's scope: a member declaration can
8028/// live in a file other than its class's only when it is written out of line.
8029/// A file a build concatenates rather than compiles carries no `#include` edge
8030/// to the header declaring `Owner` -- google/wuffs
8031/// `internal/cgen/auxiliary/image.cc` defines
8032/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
8033/// every unqualified member and constructor reference in it had no candidate at
8034/// all (#1832).
8035///
8036/// The evidence is the indexed declaration's own owner name, taken from its
8037/// `FqName`, so this stays a structured answer rather than a text fallback.
8038/// Only an owner the file cannot already see is admitted: that is what keeps a
8039/// header declaring its own class from additionally seeing every same-named
8040/// class in the workspace, and it makes the pass free for the ordinary file
8041/// whose owners are all visible.
8042#[derive(Default)]
8043struct OutOfLineOwnerBindingStats {
8044    unseen_owners: usize,
8045    definition_lookups: usize,
8046    admitted: usize,
8047}
8048
8049fn extend_with_out_of_line_owner_bindings(
8050    cpp: &dyn CppSource,
8051    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
8052) -> OutOfLineOwnerBindingStats {
8053    let mut stats = OutOfLineOwnerBindingStats::default();
8054    for (file, visible) in visible_by_file.iter_mut() {
8055        // The include-closure walk seeds every root with its own declarations,
8056        // so the file's members are already here; re-reading them from the
8057        // analyzer would pay for the same declaration set twice.
8058        let mut unseen_owners: HashSet<String> = visible
8059            .iter()
8060            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
8061            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
8062            .collect();
8063        if unseen_owners.is_empty() {
8064            continue;
8065        }
8066        for unit in visible.iter().filter(|unit| unit.is_class()) {
8067            unseen_owners.remove(&unit.fq_name());
8068        }
8069        stats.unseen_owners += unseen_owners.len();
8070        stats.definition_lookups += unseen_owners.len();
8071        let admitted = unseen_owners
8072            .iter()
8073            .flat_map(|owner| cpp.definitions(owner))
8074            .filter(CodeUnit::is_class)
8075            .collect::<Vec<_>>();
8076        stats.admitted += admitted.len();
8077        visible.extend(admitted);
8078    }
8079    stats
8080}
8081
8082pub enum VisibleMemberResolution {
8083    Callable(Vec<CodeUnit>),
8084    NonCallable,
8085    AmbiguousKind,
8086    Missing,
8087}
8088
8089#[derive(Clone)]
8090pub enum EnclosingMemberOwnerResolution {
8091    Owner(CodeUnit),
8092    Ambiguous,
8093    Missing,
8094}
8095
8096pub fn resolve_declaring_member_owner(
8097    analyzer: &CppGraphSource<'_>,
8098    visibility: &VisibilityIndex<'_>,
8099    file: &ProjectFile,
8100    receiver_owner: &CodeUnit,
8101    member_name: &str,
8102) -> EnclosingMemberOwnerResolution {
8103    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
8104        return EnclosingMemberOwnerResolution::Missing;
8105    };
8106    let Some(receiver_owner) =
8107        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
8108    else {
8109        return EnclosingMemberOwnerResolution::Ambiguous;
8110    };
8111    let resolve_level = |frontier: &[CodeUnit]| {
8112        let mut member_owners = Vec::new();
8113        for raw_owner in frontier {
8114            let Some(owner) =
8115                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
8116            else {
8117                return EnclosingMemberOwnerResolution::Ambiguous;
8118            };
8119            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
8120                let Some(member_owner) = type_owner_of(analyzer, member) else {
8121                    return EnclosingMemberOwnerResolution::Ambiguous;
8122                };
8123                if !member_owners
8124                    .iter()
8125                    .any(|existing| same_visible_symbol(existing, &member_owner))
8126                {
8127                    member_owners.push(member_owner);
8128                }
8129            }
8130        }
8131        match member_owners.len() {
8132            0 => EnclosingMemberOwnerResolution::Missing,
8133            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
8134            _ => EnclosingMemberOwnerResolution::Ambiguous,
8135        }
8136    };
8137    // The first declaration on each structured base path hides deeper names,
8138    // regardless of whether its callable overload is applicable at a particular
8139    // call site. Applicability is checked only after this owner is established.
8140    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
8141    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
8142        return direct;
8143    }
8144    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
8145    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
8146    let mut path_matches = Vec::new();
8147    while let Some(raw_owner) = stack.pop() {
8148        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
8149        else {
8150            return EnclosingMemberOwnerResolution::Ambiguous;
8151        };
8152        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
8153        // Propagate at most two occurrences of each owner: that preserves the distinction
8154        // between one and multiple resolving base paths without exponential diamond walks.
8155        let propagated = propagated_counts.entry(owner.clone()).or_default();
8156        if *propagated == 2 {
8157            continue;
8158        }
8159        *propagated += 1;
8160        match resolve_level(std::slice::from_ref(&owner)) {
8161            EnclosingMemberOwnerResolution::Owner(owner) => {
8162                path_matches.push(owner);
8163                if path_matches.len() == 2 {
8164                    return EnclosingMemberOwnerResolution::Ambiguous;
8165                }
8166            }
8167            EnclosingMemberOwnerResolution::Ambiguous => {
8168                return EnclosingMemberOwnerResolution::Ambiguous;
8169            }
8170            EnclosingMemberOwnerResolution::Missing => {
8171                stack.extend(hierarchy.get_direct_ancestors(&owner));
8172            }
8173        }
8174    }
8175    match path_matches.len() {
8176        0 => EnclosingMemberOwnerResolution::Missing,
8177        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
8178        _ => unreachable!("base-path matches are capped at one before returning"),
8179    }
8180}
8181
8182/// Resolve the declaring owner of a callable after applying a member
8183/// `using <Base>::<member>;` declaration to one exact call arity.
8184///
8185/// Ordinary member lookup is intentionally name-based: the first class that
8186/// declares a name hides the same name on deeper bases. A member
8187/// using-declaration is the one exception. When none of the declarations on
8188/// that first owner accepts the call arity, it can reintroduce an applicable
8189/// overload from the named base. If a declaration on the first owner does
8190/// accept the arity, argument types would be needed to choose between it and
8191/// a same-arity introduced overload, so this resolver conservatively keeps the
8192/// ordinary owner (#1835/#1843).
8193///
8194/// The caller supplies ordinary name-based owner resolution so a file scan can
8195/// reuse its existing owner cache before applying this callable-only exception.
8196pub fn resolve_declaring_callable_owner(
8197    analyzer: &CppGraphSource<'_>,
8198    visibility: &VisibilityIndex<'_>,
8199    file: &ProjectFile,
8200    ordinary: EnclosingMemberOwnerResolution,
8201    member_name: &str,
8202    call_arity: usize,
8203) -> EnclosingMemberOwnerResolution {
8204    let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
8205        return ordinary;
8206    };
8207    if visibility
8208        .visible_members_for_owner_name(file, ordinary_owner, member_name)
8209        .into_iter()
8210        .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
8211    {
8212        return ordinary;
8213    }
8214
8215    let mut pending = match member_using_declaration_bases(
8216        analyzer,
8217        visibility,
8218        file,
8219        ordinary_owner,
8220        member_name,
8221    ) {
8222        Ok(bases) => bases,
8223        Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
8224    };
8225    let mut visited = HashSet::default();
8226    let mut introduced_owners = Vec::new();
8227    while let Some(owner) = pending.pop() {
8228        if !visited.insert(owner.clone()) {
8229            continue;
8230        }
8231        let accepts_arity = visibility
8232            .visible_members_for_owner_name(file, &owner, member_name)
8233            .into_iter()
8234            .any(|unit| {
8235                unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
8236            });
8237        if accepts_arity {
8238            if !introduced_owners
8239                .iter()
8240                .any(|existing| same_visible_symbol(existing, &owner))
8241            {
8242                introduced_owners.push(owner);
8243            }
8244            continue;
8245        }
8246        match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
8247            Ok(bases) => pending.extend(bases),
8248            Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
8249        }
8250    }
8251    match introduced_owners.as_slice() {
8252        [] => ordinary,
8253        [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
8254        _ => EnclosingMemberOwnerResolution::Ambiguous,
8255    }
8256}
8257
8258fn member_using_declaration_bases(
8259    analyzer: &CppGraphSource<'_>,
8260    visibility: &VisibilityIndex<'_>,
8261    file: &ProjectFile,
8262    owner: &CodeUnit,
8263    member_name: &str,
8264) -> Result<Vec<CodeUnit>, ()> {
8265    let Some(source) = analyzer.get_source(owner, false) else {
8266        return Ok(Vec::new());
8267    };
8268    let scopes = cpp_member_using_declaration_scopes(&source, member_name);
8269    if scopes.is_empty() {
8270        return Ok(Vec::new());
8271    }
8272    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
8273        return Ok(Vec::new());
8274    };
8275    let mut bases = Vec::new();
8276    for raw_ancestor in hierarchy.get_ancestors(owner) {
8277        let Some(ancestor) =
8278            visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
8279        else {
8280            return Err(());
8281        };
8282        let qualified = cpp_name_for(&ancestor);
8283        if scopes
8284            .iter()
8285            .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
8286            && !bases
8287                .iter()
8288                .any(|existing| same_visible_symbol(existing, &ancestor))
8289        {
8290            bases.push(ancestor);
8291        }
8292    }
8293    Ok(bases)
8294}
8295
8296pub fn lexical_component_tiers<'a>(
8297    components: &'a [String],
8298    global: bool,
8299    lexical_scope: &'a [String],
8300) -> impl Iterator<Item = Vec<String>> + 'a {
8301    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
8302    (0..=first_prefix_len).rev().map(move |prefix_len| {
8303        let mut qualified = Vec::with_capacity(prefix_len + components.len());
8304        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
8305        qualified.extend_from_slice(components);
8306        qualified
8307    })
8308}
8309
8310pub fn build_visible_identifier_index(
8311    analyzer: &CppGraphSource<'_>,
8312    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
8313    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
8314    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
8315) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
8316    let mut out = HashMap::default();
8317    for (file, visible) in visible_by_file {
8318        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
8319        for unit in visible {
8320            if unit.is_field()
8321                && !visible_source_files_by_root
8322                    .get(file)
8323                    .is_some_and(|sources| sources.contains(unit.source()))
8324                && cpp_global_field_has_internal_linkage_cached(
8325                    analyzer,
8326                    global_field_internal_linkage,
8327                    unit,
8328                )
8329            {
8330                continue;
8331            }
8332            by_identifier
8333                .entry(unit.identifier().to_string())
8334                .or_default()
8335                .push(unit.clone());
8336        }
8337        for units in by_identifier.values_mut() {
8338            sort_lookup_units(units);
8339            units.dedup();
8340        }
8341        out.insert(file.clone(), by_identifier);
8342    }
8343    out
8344}
8345
8346fn sort_lookup_units(units: &mut [CodeUnit]) {
8347    units.sort_by(|left, right| {
8348        left.fq_name()
8349            .cmp(&right.fq_name())
8350            .then_with(|| left.signature().cmp(&right.signature()))
8351            .then_with(|| left.source().cmp(right.source()))
8352            .then_with(|| left.kind().cmp(&right.kind()))
8353            .then_with(|| {
8354                left.package_segment_count()
8355                    .cmp(&right.package_segment_count())
8356            })
8357            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
8358            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
8359    });
8360}
8361
8362fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
8363    let interner = segment_interner();
8364    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
8365        let (left_text, left_kind) = interner.resolve(left_id);
8366        let (right_text, right_kind) = interner.resolve(right_id);
8367        let order = left_text
8368            .cmp(right_text)
8369            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
8370        if order != CmpOrdering::Equal {
8371            return order;
8372        }
8373    }
8374    left.len().cmp(&right.len())
8375}
8376
8377const fn segment_kind_order(kind: SegmentKind) -> u8 {
8378    match kind {
8379        SegmentKind::Path => 0,
8380        SegmentKind::Package => 1,
8381        SegmentKind::Type => 2,
8382        SegmentKind::Companion => 3,
8383        SegmentKind::Nested => 4,
8384        SegmentKind::Member => 5,
8385        SegmentKind::Unknown => 6,
8386    }
8387}
8388
8389fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
8390    let mut deduped = Vec::with_capacity(units.len());
8391    for unit in units.drain(..) {
8392        if !deduped.contains(&unit) {
8393            deduped.push(unit);
8394        }
8395    }
8396    *units = deduped;
8397}
8398
8399pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
8400    // Same domain as `candidate_units` above: `reference` is a plain
8401    // `::`-joined qualified-id with operator tokens kept intact by the shared
8402    // splitter's operator merge.
8403    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8404        brokk_bifrost_core::analyzer::Language::Cpp,
8405        reference,
8406    );
8407    if parts.is_empty() {
8408        return Vec::new();
8409    }
8410
8411    let mut candidates = Vec::new();
8412    for package_len in 0..parts.len() {
8413        let package = parts[..package_len].join("::");
8414        let rest = &parts[package_len..];
8415        if rest.is_empty() {
8416            continue;
8417        }
8418        match kind {
8419            TargetKind::Type | TargetKind::Constructor => {
8420                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
8421                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
8422            }
8423            TargetKind::FreeFunction
8424            | TargetKind::Method
8425            | TargetKind::GlobalField
8426            | TargetKind::MemberField
8427            | TargetKind::Macro => {
8428                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
8429                if rest.len() > 1 {
8430                    let owner = rest[..rest.len() - 1].join("$");
8431                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
8432                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
8433                }
8434            }
8435        }
8436    }
8437    candidates
8438}
8439
8440fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
8441    let fqn = if package.is_empty() {
8442        short.to_string()
8443    } else {
8444        format!("{package}.{short}")
8445    };
8446    if !out.contains(&fqn) {
8447        out.push(fqn);
8448    }
8449}
8450
8451pub fn infer_cpp_initializer_type(
8452    analyzer: &CppGraphSource<'_>,
8453    visibility: &VisibilityIndex<'_>,
8454    file: &ProjectFile,
8455    source: &str,
8456    node: Node<'_>,
8457) -> Option<CodeUnit> {
8458    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
8459        .and_then(|binding| binding.unit)
8460}
8461
8462pub fn infer_cpp_initializer_binding(
8463    analyzer: &CppGraphSource<'_>,
8464    visibility: &VisibilityIndex<'_>,
8465    file: &ProjectFile,
8466    source: &str,
8467    node: Node<'_>,
8468    receiver_resolver: Option<&ReceiverResolver<'_>>,
8469) -> Option<CppScanBinding> {
8470    match node.kind() {
8471        "new_expression" => {
8472            let text = normalize_cpp_whitespace(node_text(node, source));
8473            let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
8474            let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
8475            let name = normalize_cpp_type_name(type_text);
8476            Some(CppScanBinding::from_type_name(
8477                name.clone(),
8478                visibility.resolve_type(file, &name),
8479                1,
8480            ))
8481        }
8482        "call_expression" => node.child_by_field_name("function").and_then(|function| {
8483            // `a().b()` and `p->b()` invoke a member on a receiver *value*. The
8484            // callee's source text is an expression, not a name, and every name
8485            // lookup below normalizes a reference by truncating at the first
8486            // `(`: `first().second` would read as `first`, so the chained call
8487            // would take the type of `first()` instead of the type of
8488            // `first().second()` (#2178). Only the member path can answer for
8489            // this shape, so route to it from the callee's node kind.
8490            if function.kind() == "field_expression" {
8491                let arity = visibility.call_arity_evidence(file, node, source).exact()?;
8492                return resolve_field_method_call_return_binding(
8493                    analyzer,
8494                    visibility,
8495                    file,
8496                    source,
8497                    function,
8498                    arity,
8499                    receiver_resolver,
8500                );
8501            }
8502            let function_text = node_text(function, source);
8503            let direct_type_binding = visibility
8504                .resolve_type(file, function_text)
8505                .map(|unit| CppScanBinding::from_unit(unit, 0));
8506            if function.kind() == "template_function" && direct_type_binding.is_some() {
8507                let lexical_namespace = enclosing_namespace_context(node, source);
8508                let arity = visibility.call_arity_evidence(file, node, source).exact();
8509                if let Some(arity) = arity
8510                    && let Some(binding) = visibility.resolve_call_return_binding(
8511                        analyzer,
8512                        file,
8513                        function_text,
8514                        arity,
8515                        lexical_namespace.as_deref(),
8516                        direct_type_binding
8517                            .as_ref()
8518                            .and_then(|binding| binding.unit.as_ref()),
8519                    )
8520                {
8521                    return Some(binding);
8522                }
8523                let (has_callable, callable_binding) = visibility
8524                    .resolve_call_return_binding_without_arity(
8525                        analyzer,
8526                        file,
8527                        function_text,
8528                        lexical_namespace.as_deref(),
8529                        direct_type_binding
8530                            .as_ref()
8531                            .and_then(|binding| binding.unit.as_ref()),
8532                    );
8533                if let Some(binding) = callable_binding {
8534                    return Some(binding);
8535                }
8536                if has_callable {
8537                    return None;
8538                }
8539                return direct_type_binding;
8540            }
8541            // Only the return-typed branches need the argument count. An
8542            // unknown arity leaves them out, exactly as in the template arm
8543            // above, and still constructs the direct type: `File(getPath())`
8544            // names `File` whether or not `getPath()`'s expansion is provable.
8545            let arity = visibility.call_arity_evidence(file, node, source).exact();
8546            if let Some(arity) = arity {
8547                let direct_type_binding_for_call = direct_type_binding.clone();
8548                if let Some(binding) = resolve_static_method_call_return_binding(
8549                    analyzer, visibility, file, source, function, arity,
8550                )
8551                .or_else(|| {
8552                    // An applicable free function supplies the receiver value
8553                    // before an unrelated visible type with the same terminal
8554                    // name. The direct type still excludes its own constructor
8555                    // declaration below and remains the construction fallback.
8556                    visibility.resolve_call_return_binding(
8557                        analyzer,
8558                        file,
8559                        function_text,
8560                        arity,
8561                        enclosing_namespace_context(node, source).as_deref(),
8562                        direct_type_binding_for_call
8563                            .as_ref()
8564                            .and_then(|binding| binding.unit.as_ref()),
8565                    )
8566                }) {
8567                    return Some(binding);
8568                }
8569            }
8570            direct_type_binding
8571        }),
8572        _ => None,
8573    }
8574}
8575
8576fn resolve_static_method_call_return_binding(
8577    analyzer: &CppGraphSource<'_>,
8578    visibility: &VisibilityIndex<'_>,
8579    file: &ProjectFile,
8580    source: &str,
8581    function: Node<'_>,
8582    arity: usize,
8583) -> Option<CppScanBinding> {
8584    if function.kind() != "qualified_identifier" {
8585        return None;
8586    }
8587    let qualified = normalize_cpp_reference_text(node_text(function, source));
8588    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
8589    // single component (the shared splitter's operator-token merge keeps
8590    // `operator+`-style names intact), so re-tokenizing with the shared
8591    // structured splitter and peeling the terminal segment reproduces
8592    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
8593    // `cpp_out_of_line_function_owner`'s `qualified` split above.
8594    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8595        brokk_bifrost_core::analyzer::Language::Cpp,
8596        &qualified,
8597    );
8598    let (owner_text, member_name) = match parts.split_last() {
8599        Some((member, owner_parts)) if !owner_parts.is_empty() => {
8600            (owner_parts.join("::"), member.clone())
8601        }
8602        _ => {
8603            let scope = function.child_by_field_name("scope")?;
8604            let name = function.child_by_field_name("name")?;
8605            (
8606                node_text(scope, source).to_string(),
8607                node_text(name, source).to_string(),
8608            )
8609        }
8610    };
8611    let owner = visibility.resolve_type(file, &owner_text)?;
8612    let candidates = visibility
8613        .visible_members_for_owner_name(file, &owner, &member_name)
8614        .into_iter()
8615        .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
8616        .cloned()
8617        .collect::<Vec<_>>();
8618    unanimous_return_binding(analyzer, visibility, file, &candidates)
8619}
8620
8621fn resolve_field_method_call_return_binding(
8622    analyzer: &CppGraphSource<'_>,
8623    visibility: &VisibilityIndex<'_>,
8624    file: &ProjectFile,
8625    source: &str,
8626    function: Node<'_>,
8627    arity: usize,
8628    receiver_resolver: Option<&ReceiverResolver<'_>>,
8629) -> Option<CppScanBinding> {
8630    debug_assert_eq!(
8631        function.kind(),
8632        "field_expression",
8633        "the member-call return binding answers only for a field-expression callee"
8634    );
8635    let receiver_resolver = receiver_resolver?;
8636    let field = function.child_by_field_name("field")?;
8637    let member_name = node_text(function_terminal_node(field), source);
8638    let receiver = function
8639        .child_by_field_name("argument")
8640        .or_else(|| function.named_child(0))?;
8641    let owners = receiver_resolver(receiver, source);
8642    let mut candidates = Vec::new();
8643    for owner in owners {
8644        let declaring_owner =
8645            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
8646                EnclosingMemberOwnerResolution::Owner(owner) => owner,
8647                EnclosingMemberOwnerResolution::Missing => continue,
8648                EnclosingMemberOwnerResolution::Ambiguous => return None,
8649            };
8650        candidates.extend(
8651            visibility
8652                .visible_members_for_owner_name(file, &declaring_owner, member_name)
8653                .into_iter()
8654                .filter(|unit| {
8655                    unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
8656                })
8657                .cloned(),
8658        );
8659    }
8660    unanimous_return_binding(analyzer, visibility, file, &candidates)
8661}
8662
8663fn unanimous_return_binding(
8664    analyzer: &CppGraphSource<'_>,
8665    visibility: &VisibilityIndex<'_>,
8666    file: &ProjectFile,
8667    candidates: &[CodeUnit],
8668) -> Option<CppScanBinding> {
8669    let mut resolved_return: Option<CppScanBinding> = None;
8670    for function in candidates {
8671        let metadata = analyzer.signature_metadata(function);
8672        let return_types = if metadata.is_empty() {
8673            vec![cpp_function_return_type_text(analyzer, function)?]
8674        } else {
8675            metadata
8676                .iter()
8677                .map(|metadata| metadata.return_type_text().map(str::to_string))
8678                .collect::<Option<Vec<_>>>()?
8679        };
8680        for return_text in return_types {
8681            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
8682            let name = normalize_cpp_type_name(&return_text);
8683            let binding = CppScanBinding::from_type_name(
8684                name.clone(),
8685                visibility
8686                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
8687                indirection,
8688            );
8689            if let Some(existing) = resolved_return.as_ref()
8690                && (existing.indirection != binding.indirection
8691                    || match (&existing.unit, &binding.unit) {
8692                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
8693                        (None, None) => existing.type_name != binding.type_name,
8694                        (Some(_), None) | (None, Some(_)) => true,
8695                    })
8696            {
8697                return None;
8698            }
8699            resolved_return = Some(binding);
8700        }
8701    }
8702    resolved_return
8703}
8704
8705fn aliases_from_prepared_source(
8706    cpp: &dyn CppSource,
8707    token: QueryToken<'_>,
8708    file: &ProjectFile,
8709) -> Vec<CppAlias> {
8710    let Some(prepared) = cpp.prepared_syntax(token, file) else {
8711        return Vec::new();
8712    };
8713    let mut aliases = Vec::new();
8714    collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
8715    aliases
8716}
8717
8718fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
8719    let mut stack = vec![root];
8720    while let Some(node) = stack.pop() {
8721        match node.kind() {
8722            "alias_declaration" if alias_has_visible_file_scope(node) => {
8723                if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
8724                    out.push(alias);
8725                }
8726            }
8727            "type_definition" if alias_has_visible_file_scope(node) => {
8728                collect_typedef_aliases(node, source, out)
8729            }
8730            _ => {}
8731        }
8732
8733        for index in (0..node.named_child_count()).rev() {
8734            if let Some(child) = node.named_child(index) {
8735                stack.push(child);
8736            }
8737        }
8738    }
8739}
8740
8741fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
8742    let mut current = node.parent();
8743    while let Some(parent) = current {
8744        match parent.kind() {
8745            "translation_unit"
8746            | "namespace_definition"
8747            | "declaration_list"
8748            | "linkage_specification" => current = parent.parent(),
8749            "template_declaration" => current = parent.parent(),
8750            _ => return false,
8751        }
8752    }
8753    true
8754}
8755
8756fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
8757    let name = node
8758        .child_by_field_name("name")
8759        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
8760    let target = node
8761        .child_by_field_name("type")
8762        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
8763    Some(CppAlias {
8764        name,
8765        target,
8766        namespace: enclosing_namespace_context(node, source),
8767    })
8768}
8769
8770fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
8771    let Some(type_node) = node.child_by_field_name("type") else {
8772        return;
8773    };
8774    let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
8775        return;
8776    };
8777
8778    let mut cursor = node.walk();
8779    for child in node.named_children(&mut cursor) {
8780        if same_node(child, type_node) {
8781            continue;
8782        }
8783        if let Some(name) = extract_typedef_declarator_name(child, source) {
8784            out.push(CppAlias {
8785                name,
8786                target: target.clone(),
8787                namespace: enclosing_namespace_context(node, source),
8788            });
8789        }
8790    }
8791}
8792
8793fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
8794    match node.kind() {
8795        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
8796            normalize_reference_name(node_text(node, source))
8797        }
8798        _ => node
8799            .child_by_field_name("declarator")
8800            .or_else(|| node.child_by_field_name("name"))
8801            .or_else(|| last_named_child(node))
8802            .and_then(|child| extract_typedef_declarator_name(child, source)),
8803    }
8804}
8805
8806fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
8807    let count = node.named_child_count();
8808    if count == 0 {
8809        None
8810    } else {
8811        node.named_child(count - 1)
8812    }
8813}
8814
8815pub fn collect_include_closure(
8816    analyzer: &CppGraphSource<'_>,
8817    include_targets: &IncludeTargetIndex,
8818    file: &ProjectFile,
8819    out: &mut HashSet<ProjectFile>,
8820    cancellation: Option<&CancellationToken>,
8821) {
8822    let mut stack = vec![file.clone()];
8823    while let Some(file) = stack.pop() {
8824        if cancellation.is_some_and(CancellationToken::is_cancelled) {
8825            break;
8826        }
8827        if !out.insert(file.clone()) {
8828            continue;
8829        }
8830        let imports = analyzer.import_statements(&file);
8831        for include in cpp_include_paths(&imports) {
8832            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
8833                stack.push(target);
8834            }
8835        }
8836    }
8837}
8838
8839fn collect_visible_declarations(
8840    include_graph: &IncludeGraph,
8841    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
8842    file: &ProjectFile,
8843    visited: &mut HashSet<ProjectFile>,
8844    out: &mut HashSet<CodeUnit>,
8845    cancellation: Option<&CancellationToken>,
8846) {
8847    let mut stack = vec![file.clone()];
8848    while let Some(file) = stack.pop() {
8849        if cancellation.is_some_and(CancellationToken::is_cancelled) {
8850            break;
8851        }
8852        if !visited.insert(file.clone()) {
8853            continue;
8854        }
8855        if let Some(declarations) = declarations_by_file.get(&file) {
8856            out.extend(declarations.iter().cloned());
8857        }
8858        stack.extend(include_graph.targets(&file).iter().cloned());
8859    }
8860}
8861
8862pub fn signature_arity(signature: Option<&str>) -> usize {
8863    let Some(signature) = signature else {
8864        return 0;
8865    };
8866    let inner = signature
8867        .find('(')
8868        .and_then(|open| {
8869            signature[open + 1..]
8870                .find(')')
8871                .map(|close| &signature[open + 1..open + 1 + close])
8872        })
8873        .unwrap_or(signature)
8874        .trim();
8875    if inner.is_empty() || inner == "void" {
8876        return 0;
8877    }
8878    cpp_split_top_level_commas(inner).count()
8879}
8880
8881fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
8882    let source = format!("void __bifrost_macro_parameters({replacement});");
8883    let mut parser = Parser::new();
8884    parser
8885        .set_language(&tree_sitter_cpp::LANGUAGE.into())
8886        .ok()?;
8887    let tree = parser.parse(&source, None)?;
8888    let root = tree.root_node();
8889    if root.has_error() {
8890        return None;
8891    }
8892    let declaration = root.named_child(0)?;
8893    let declarator = declaration.child_by_field_name("declarator")?;
8894    let parameters = declarator.child_by_field_name("parameters")?;
8895    let mut required = 0;
8896    let mut total = 0;
8897    let mut repeated = false;
8898    let mut cursor = parameters.walk();
8899    for parameter in parameters.children(&mut cursor) {
8900        match parameter.kind() {
8901            "parameter_declaration" => {
8902                if parameter.child_by_field_name("declarator").is_none()
8903                    && parameter
8904                        .child_by_field_name("type")
8905                        .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
8906                {
8907                    continue;
8908                }
8909                required += 1;
8910                total += 1;
8911            }
8912            "optional_parameter_declaration" => total += 1,
8913            "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
8914                repeated = true;
8915            }
8916            _ => {}
8917        }
8918    }
8919    Some(CallableArity::new(required, total, repeated))
8920}
8921
8922pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
8923    analyzer
8924        .signature_metadata(unit)
8925        .into_iter()
8926        .find_map(|metadata| metadata.callable_arity())
8927        .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
8928}
8929
8930pub fn cpp_callable_parameter_types(
8931    analyzer: &CppGraphSource<'_>,
8932    unit: &CodeUnit,
8933) -> Option<Vec<String>> {
8934    analyzer
8935        .signature_metadata(unit)
8936        .into_iter()
8937        .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
8938        .or_else(|| unit.signature().and_then(cpp_signature_param_types))
8939}
8940
8941fn merge_compatible_callable_arities(
8942    left: CallableArity,
8943    right: CallableArity,
8944) -> Option<CallableArity> {
8945    let total = left.total();
8946    let left_repeated = left.accepts(total.saturating_add(1));
8947    let right_repeated = right.accepts(right.total().saturating_add(1));
8948    if total != right.total() || left_repeated != right_repeated {
8949        return None;
8950    }
8951    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
8952    Some(CallableArity::new(required, total, left_repeated))
8953}
8954
8955fn find_include_activation(
8956    cpp: &dyn CppSource,
8957    token: QueryToken<'_>,
8958    file: &ProjectFile,
8959    prepared: &PreparedSyntaxTree,
8960    donor_source: &ProjectFile,
8961) -> Option<usize> {
8962    let include_targets = cpp.include_target_index();
8963    let mut direct_includes = Vec::new();
8964    let mut nodes = vec![prepared.tree().root_node()];
8965    // An include activates for the whole file, so only an unconditional
8966    // directive counts here.
8967    let reference = CallableReferenceContext {
8968        file,
8969        position: None,
8970    };
8971    while let Some(node) = nodes.pop() {
8972        if node.kind() == "preproc_include" {
8973            if callable_preprocessor_context_is_visible_for_reference(
8974                node,
8975                prepared.source(),
8976                &reference,
8977            ) {
8978                let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
8979                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8980                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
8981                        file,
8982                        &include,
8983                        include_targets,
8984                    )) {
8985                        direct_includes.push((node.end_byte(), target));
8986                    }
8987                }
8988            }
8989            continue;
8990        }
8991        for index in (0..node.named_child_count()).rev() {
8992            if let Some(child) = node.named_child(index) {
8993                nodes.push(child);
8994            }
8995        }
8996    }
8997    direct_includes.sort_by_key(|(activation, _)| *activation);
8998    let mut known_missing = HashSet::default();
8999    direct_includes
9000        .into_iter()
9001        .find(|(_, direct)| {
9002            unconditional_include_reaches(
9003                cpp,
9004                token,
9005                include_targets,
9006                direct,
9007                donor_source,
9008                file,
9009                &mut known_missing,
9010            )
9011        })
9012        .map(|(activation, _)| activation)
9013}
9014
9015fn find_conditional_include_projection_index(
9016    cpp: &dyn CppSource,
9017    token: QueryToken<'_>,
9018    file: &ProjectFile,
9019    prepared: &PreparedSyntaxTree,
9020    on_state: &dyn Fn(),
9021) -> ConditionalIncludeProjectionIndex {
9022    let include_targets = cpp.include_target_index();
9023    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
9024        HashMap::default();
9025    let mut pending = Vec::new();
9026    let mut nodes = vec![prepared.tree().root_node()];
9027    while let Some(node) = nodes.pop() {
9028        if node.kind() == "preproc_include" {
9029            let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
9030            else {
9031                continue;
9032            };
9033            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9034            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9035                let Some(target) = unique_include_target(resolve_include_targets_with_index(
9036                    file,
9037                    &include,
9038                    include_targets,
9039                )) else {
9040                    continue;
9041                };
9042                pending.push((target, node.end_byte(), required_guards.clone()));
9043            }
9044            continue;
9045        }
9046        for index in (0..node.named_child_count()).rev() {
9047            if let Some(child) = node.named_child(index) {
9048                nodes.push(child);
9049            }
9050        }
9051    }
9052
9053    // One reached file can have several distinct compatible guard paths. Each
9054    // (file, activation byte) key keeps only the inclusion-minimal guard sets:
9055    // the consumers ask existence questions whose answers are monotone in the
9056    // guard set -- a path whose requirements hold, stay stable, and stay
9057    // compatible under one environment does so under every subset as well --
9058    // so a state subsumed by an existing subset cannot witness anything its
9059    // subset does not, and inserting a smaller set evicts the supersets it
9060    // subsumes. Exact-set dedup still terminated cycles, but dense `#ifdef`
9061    // lattices (QMK's per-keyboard feature guards) enumerated the powerset of
9062    // path-union guard sets through it: the state space, the per-key linear
9063    // scans, and resident memory all grew without bound (#2365).
9064    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
9065        HashMap::default();
9066    while let Some((current_file, activation_byte, required_guards)) = pending.pop() {
9067        let guard_sets = expanded
9068            .entry((current_file.clone(), activation_byte))
9069            .or_default();
9070        if guard_sets
9071            .iter()
9072            .any(|existing| existing.is_subset(&required_guards))
9073        {
9074            continue;
9075        }
9076        let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
9077            .drain(..)
9078            .partition(|existing| required_guards.is_subset(existing));
9079        *guard_sets = kept;
9080        guard_sets.push(required_guards.clone());
9081        if !evicted.is_empty()
9082            && let Some(projections) = projections_by_source.get_mut(&current_file)
9083        {
9084            projections.retain(|projection| {
9085                projection.activation_byte != activation_byte
9086                    || !evicted.contains(&projection.required_guards)
9087            });
9088        }
9089        on_state();
9090
9091        // A fresh minimal set has no equal in the store: equality would have
9092        // been caught by the subset check above.
9093        projections_by_source
9094            .entry(current_file.clone())
9095            .or_default()
9096            .push(ConditionalIncludeProjection {
9097                activation_byte,
9098                required_guards: required_guards.clone(),
9099            });
9100
9101        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
9102            continue;
9103        };
9104        let mut nodes = vec![current_prepared.tree().root_node()];
9105        while let Some(node) = nodes.pop() {
9106            if node.kind() == "preproc_include" {
9107                let Some(include_guards) =
9108                    preprocessor_guard_environment(node, current_prepared.source())
9109                else {
9110                    continue;
9111                };
9112                let Some(path_guards) =
9113                    merge_preprocessor_guards(&required_guards, &include_guards)
9114                else {
9115                    continue;
9116                };
9117                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
9118                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9119                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
9120                        &current_file,
9121                        &include,
9122                        include_targets,
9123                    )) else {
9124                        continue;
9125                    };
9126                    pending.push((target, activation_byte, path_guards.clone()));
9127                }
9128                continue;
9129            }
9130            for index in (0..node.named_child_count()).rev() {
9131                if let Some(child) = node.named_child(index) {
9132                    nodes.push(child);
9133                }
9134            }
9135        }
9136    }
9137
9138    projections_by_source
9139        .into_iter()
9140        .map(|(source, mut projections)| {
9141            projections.sort_by_key(|projection| projection.activation_byte);
9142            (source, Arc::from(projections))
9143        })
9144        .collect()
9145}
9146
9147/// Decide one conditional include target without materializing every source
9148/// reached by every guard combination. Paths whose requirements do not hold
9149/// at the reference cannot become feasible after adding nested include guards,
9150/// so discard them before expanding the next header.
9151#[allow(clippy::too_many_arguments)]
9152fn find_conditional_include_projection_for_source(
9153    cpp: &dyn CppSource,
9154    token: QueryToken<'_>,
9155    file: &ProjectFile,
9156    prepared: &PreparedSyntaxTree,
9157    donor_source: &ProjectFile,
9158    reference_guards: Option<&HashSet<PreprocessorGuard>>,
9159    reference_byte: usize,
9160    on_state: &dyn Fn(),
9161) -> bool {
9162    let Some(reference_guards) = reference_guards else {
9163        return false;
9164    };
9165    let include_targets = cpp.include_target_index();
9166    let mut pending = Vec::new();
9167    let mut nodes = vec![prepared.tree().root_node()];
9168    while let Some(node) = nodes.pop() {
9169        if node.kind() == "preproc_include" {
9170            let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
9171            else {
9172                continue;
9173            };
9174            if node.end_byte() > reference_byte
9175                || !guard_requirements_hold_at_reference(&required_guards, Some(reference_guards))
9176            {
9177                continue;
9178            }
9179            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9180            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9181                let Some(target) = unique_include_target(resolve_include_targets_with_index(
9182                    file,
9183                    &include,
9184                    include_targets,
9185                )) else {
9186                    continue;
9187                };
9188                if &target == donor_source {
9189                    return true;
9190                }
9191                pending.push((target, required_guards.clone()));
9192            }
9193            continue;
9194        }
9195        for index in (0..node.named_child_count()).rev() {
9196            if let Some(child) = node.named_child(index) {
9197                nodes.push(child);
9198            }
9199        }
9200    }
9201
9202    let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
9203    while let Some((current_file, required_guards)) = pending.pop() {
9204        let guard_sets = expanded.entry(current_file.clone()).or_default();
9205        if guard_sets.contains(&required_guards) {
9206            continue;
9207        }
9208        guard_sets.push(required_guards.clone());
9209        on_state();
9210
9211        let Some(current_prepared) = cpp.prepared_syntax(token, &current_file) else {
9212            continue;
9213        };
9214        let mut nodes = vec![current_prepared.tree().root_node()];
9215        while let Some(node) = nodes.pop() {
9216            if node.kind() == "preproc_include" {
9217                let Some(include_guards) =
9218                    preprocessor_guard_environment(node, current_prepared.source())
9219                else {
9220                    continue;
9221                };
9222                let Some(path_guards) =
9223                    merge_preprocessor_guards(&required_guards, &include_guards)
9224                else {
9225                    continue;
9226                };
9227                if !guard_requirements_hold_at_reference(&path_guards, Some(reference_guards)) {
9228                    continue;
9229                }
9230                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
9231                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9232                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
9233                        &current_file,
9234                        &include,
9235                        include_targets,
9236                    )) else {
9237                        continue;
9238                    };
9239                    if &target == donor_source {
9240                        return true;
9241                    }
9242                    pending.push((target, path_guards.clone()));
9243                }
9244                continue;
9245            }
9246            for index in (0..node.named_child_count()).rev() {
9247                if let Some(child) = node.named_child(index) {
9248                    nodes.push(child);
9249                }
9250            }
9251        }
9252    }
9253    false
9254}
9255
9256/// Whether `translation_unit`'s unconditional `#include` closure reaches
9257/// `header`, directly or through any chain of headers.
9258///
9259/// The include-closure question asked on its own, for
9260/// [`crate::identity::cpp_header_body_files_are_related`]. The walk resolves
9261/// each include the way visibility does -- to a unique target or to nothing --
9262/// so a duplicated basename relates nothing, and it is memoized per file pair
9263/// on the analyzer.
9264///
9265/// The reference position is `translation_unit` itself: the question is
9266/// whether that unit compiles the header, so that unit's own dialect and
9267/// preprocessor context govern the walk.
9268pub fn cpp_include_closure_reaches(
9269    cpp: &dyn CppSource,
9270    token: QueryToken<'_>,
9271    translation_unit: &ProjectFile,
9272    header: &ProjectFile,
9273) -> bool {
9274    unconditional_include_reaches(
9275        cpp,
9276        token,
9277        cpp.include_target_index(),
9278        translation_unit,
9279        header,
9280        translation_unit,
9281        &mut HashSet::default(),
9282    )
9283}
9284
9285fn unconditional_include_reaches(
9286    cpp: &dyn CppSource,
9287    token: QueryToken<'_>,
9288    include_targets: &IncludeTargetIndex,
9289    first: &ProjectFile,
9290    donor_source: &ProjectFile,
9291    reference_file: &ProjectFile,
9292    known_missing: &mut HashSet<ProjectFile>,
9293) -> bool {
9294    if first == donor_source {
9295        return true;
9296    }
9297    if known_missing.contains(first) {
9298        return false;
9299    }
9300    let reference_is_c = reference_file
9301        .rel_path()
9302        .extension()
9303        .and_then(|extension| extension.to_str())
9304        == Some("c");
9305    if let Some(reaches) =
9306        cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
9307    {
9308        return reaches;
9309    }
9310    let mut visited = HashSet::default();
9311    let mut files = vec![first.clone()];
9312    // Only an unconditional directive extends the include reach, so the walk
9313    // asks the question without a reference position.
9314    let reference = CallableReferenceContext {
9315        file: reference_file,
9316        position: None,
9317    };
9318    while let Some(file) = files.pop() {
9319        if file == *donor_source {
9320            cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
9321            return true;
9322        }
9323        if known_missing.contains(&file) || !visited.insert(file.clone()) {
9324            continue;
9325        }
9326        let Some(prepared) = cpp.prepared_syntax(token, &file) else {
9327            continue;
9328        };
9329        let mut nodes = vec![prepared.tree().root_node()];
9330        while let Some(node) = nodes.pop() {
9331            if node.kind() == "preproc_include" {
9332                if callable_preprocessor_context_is_visible_for_reference(
9333                    node,
9334                    prepared.source(),
9335                    &reference,
9336                ) {
9337                    let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
9338                    for include in cpp_include_paths(std::slice::from_ref(&raw)) {
9339                        if let Some(target) = unique_include_target(
9340                            resolve_include_targets_with_index(&file, &include, include_targets),
9341                        ) {
9342                            files.push(target);
9343                        }
9344                    }
9345                }
9346                continue;
9347            }
9348            for index in (0..node.named_child_count()).rev() {
9349                if let Some(child) = node.named_child(index) {
9350                    nodes.push(child);
9351                }
9352            }
9353        }
9354    }
9355    known_missing.extend(visited);
9356    cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
9357    false
9358}
9359
9360fn declaration_guard_requirements(
9361    analyzer: &CppGraphSource<'_>,
9362    cpp: &dyn CppSource,
9363    candidate: &CodeUnit,
9364) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
9365    let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
9366        return Vec::new();
9367    };
9368    let root = prepared.tree().root_node();
9369    analyzer
9370        .ranges(candidate)
9371        .into_iter()
9372        .filter_map(|range| {
9373            root.descendant_for_byte_range(range.start_byte, range.end_byte)
9374                .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
9375                // A class name is injected into its own body at the declaration's
9376                // introduction point, not after the complete class range. Using
9377                // the start also preserves normal before/after ordering for aliases.
9378                .map(|required| (range.start_byte, required))
9379        })
9380        .collect()
9381}
9382
9383fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
9384    analyzer
9385        .ranges(candidate)
9386        .into_iter()
9387        .map(|range| range.start_byte)
9388        .min()
9389}
9390
9391/// The macro names every configuration in `contexts` defines -- the fact set
9392/// one file's compile-database coverage proves (#2011). `None` when the
9393/// database has no entry for the file, which is different from an empty
9394/// intersection: no entry means no coverage, while an empty intersection is
9395/// covered-and-proves-nothing.
9396fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
9397    let (first, rest) = contexts.split_first()?;
9398    Some(
9399        first
9400            .defined_macros
9401            .iter()
9402            .filter(|name| {
9403                rest.iter()
9404                    .all(|context| context.defined_macros.contains(*name))
9405            })
9406            .cloned()
9407            .collect(),
9408    )
9409}
9410
9411fn guard_requirements_hold_at_reference(
9412    required: &HashSet<PreprocessorGuard>,
9413    reference: Option<&HashSet<PreprocessorGuard>>,
9414) -> bool {
9415    reference.is_some_and(|active| {
9416        required
9417            .iter()
9418            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
9419    })
9420}
9421
9422fn preprocessor_guard_holds_at_reference(
9423    required: &PreprocessorGuard,
9424    active: &HashSet<PreprocessorGuard>,
9425) -> bool {
9426    if active.contains(required) {
9427        return true;
9428    }
9429    let active_expression = BooleanGuardExpression::all(
9430        active
9431            .iter()
9432            .filter_map(PreprocessorGuard::as_boolean_expression),
9433    );
9434    required
9435        .as_boolean_expression()
9436        .is_some_and(|required| active_expression.implies(&required))
9437}
9438
9439/// Cross-file guard rule: two guard sets are compatible when neither one
9440/// contradicts the other. Use this instead of the subset test whenever the
9441/// guards come from a foreign file, which resolves its own conditionals
9442/// independently of the reference.
9443fn guards_compatible_at_reference(
9444    declaration: &HashSet<PreprocessorGuard>,
9445    reference: Option<&HashSet<PreprocessorGuard>>,
9446) -> bool {
9447    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
9448}
9449
9450/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
9451/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
9452/// conditional.
9453///
9454/// Two declarations of one name that report the same chain stand in different
9455/// branches of it, so at most one of them is compiled in any configuration.
9456/// They are alternate spellings of a single declaration, not competing
9457/// declarations, and navigation must not present them as an ambiguity.
9458pub fn preprocessor_conditional_family_range(
9459    root: Node<'_>,
9460    start_byte: usize,
9461    end_byte: usize,
9462) -> Option<(usize, usize)> {
9463    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
9464    let mut ancestor = Some(node);
9465    while let Some(current) = ancestor {
9466        if is_preprocessor_conditional(current)
9467            && preprocessor_conditional_contains_descendant(current, node)
9468        {
9469            let family = preprocessor_conditional_family_root(current);
9470            return Some((family.start_byte(), family.end_byte()));
9471        }
9472        ancestor = current.parent();
9473    }
9474    None
9475}
9476
9477fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
9478    let mut ancestor = node.parent();
9479    while let Some(current) = ancestor {
9480        if is_preprocessor_conditional(current)
9481            && preprocessor_conditional_contains_descendant(current, node)
9482        {
9483            let family = preprocessor_conditional_family_root(current);
9484            if preprocessor_conditional_family_has_terminal_else(family) {
9485                return Some(family);
9486            }
9487        }
9488        ancestor = current.parent();
9489    }
9490    None
9491}
9492
9493fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
9494    while let Some(parent) = conditional.parent() {
9495        let is_alternative = parent
9496            .child_by_field_name("alternative")
9497            .is_some_and(|alternative| {
9498                alternative.start_byte() == conditional.start_byte()
9499                    && alternative.end_byte() == conditional.end_byte()
9500            });
9501        if !is_alternative {
9502            break;
9503        }
9504        conditional = parent;
9505    }
9506    conditional
9507}
9508
9509fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
9510    loop {
9511        let Some(alternative) = conditional.child_by_field_name("alternative") else {
9512            return false;
9513        };
9514        match alternative.kind() {
9515            "preproc_else" => return true,
9516            "preproc_elif" => conditional = alternative,
9517            _ => return false,
9518        }
9519    }
9520}
9521
9522pub fn preprocessor_guard_environment(
9523    node: Node<'_>,
9524    source: &str,
9525) -> Option<HashSet<PreprocessorGuard>> {
9526    let mut guards = HashSet::default();
9527    let mut ancestor = node.parent();
9528    while let Some(conditional) = ancestor {
9529        if matches!(
9530            conditional.kind(),
9531            "preproc_if" | "preproc_ifdef" | "preproc_elif"
9532        ) && !is_file_covering_include_guard(conditional, source)
9533            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
9534            && preprocessor_conditional_contains_descendant(conditional, node)
9535        {
9536            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
9537            match guard {
9538                PreprocessorGuard::Constant(true) => {
9539                    ancestor = conditional.parent();
9540                    continue;
9541                }
9542                PreprocessorGuard::Constant(false) => return None,
9543                _ => {}
9544            }
9545            if guards.contains(&guard.negated()) {
9546                return None;
9547            }
9548            guards.insert(guard);
9549        }
9550        ancestor = conditional.parent();
9551    }
9552    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
9553        match guard {
9554            PreprocessorGuard::Constant(true) => {}
9555            PreprocessorGuard::Constant(false) => return None,
9556            _ => {
9557                if guards.contains(&guard.negated()) {
9558                    return None;
9559                }
9560                guards.insert(guard);
9561            }
9562        }
9563    }
9564    Some(guards)
9565}
9566
9567fn fragmented_statement_preprocessor_guard(
9568    descendant: Node<'_>,
9569    source: &str,
9570) -> Option<PreprocessorGuard> {
9571    // A conditional that starts before `} else if (...) {` crosses the
9572    // enclosing statement's grammar boundary. tree-sitter leaves its opener
9573    // as a `preproc_if` with a missing terminator in the consequence and
9574    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
9575    // those structured nodes before restoring the guard to intervening uses.
9576    let mut ancestor = descendant.parent();
9577    while let Some(statement) = ancestor {
9578        if statement.kind() == "if_statement"
9579            && let (Some(consequence), Some(alternative)) = (
9580                statement.child_by_field_name("consequence"),
9581                statement.child_by_field_name("alternative"),
9582            )
9583            && alternative.start_byte() <= descendant.start_byte()
9584            && descendant.end_byte() <= alternative.end_byte()
9585        {
9586            let mut cursor = consequence.walk();
9587            let openers = consequence
9588                .named_children(&mut cursor)
9589                .filter(|child| {
9590                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
9591                        && child
9592                            .child(child.child_count().saturating_sub(1))
9593                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
9594                })
9595                .collect::<Vec<_>>();
9596            if openers.len() != 1 {
9597                ancestor = statement.parent();
9598                continue;
9599            }
9600
9601            let mut terminators = Vec::new();
9602            let mut stack = vec![alternative];
9603            while let Some(node) = stack.pop() {
9604                if node.kind() == "preproc_call"
9605                    && node.start_byte() >= descendant.end_byte()
9606                    && node
9607                        .child_by_field_name("directive")
9608                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
9609                {
9610                    terminators.push(node);
9611                    continue;
9612                }
9613                for index in (0..node.named_child_count()).rev() {
9614                    if let Some(child) = node.named_child(index) {
9615                        stack.push(child);
9616                    }
9617                }
9618            }
9619            if terminators.len() == 1 {
9620                return simple_preprocessor_guard(openers[0], source);
9621            }
9622        }
9623        ancestor = statement.parent();
9624    }
9625    None
9626}
9627
9628fn preprocessor_guard_for_descendant(
9629    conditional: Node<'_>,
9630    descendant: Node<'_>,
9631    source: &str,
9632) -> Option<PreprocessorGuard> {
9633    let mut guard = simple_preprocessor_guard(conditional, source)?;
9634    if conditional
9635        .child_by_field_name("alternative")
9636        .is_some_and(|alternative| {
9637            alternative.start_byte() <= descendant.start_byte()
9638                && descendant.end_byte() <= alternative.end_byte()
9639        })
9640    {
9641        let alternative = conditional.child_by_field_name("alternative")?;
9642        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
9643        // descendant in any later branch must first exclude the parent branch,
9644        // then collect the nested `preproc_elif` guard from its own ancestor.
9645        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
9646            return None;
9647        }
9648        guard = guard.negated();
9649    }
9650    Some(guard)
9651}
9652
9653fn preprocessor_conditional_contains_descendant(
9654    conditional: Node<'_>,
9655    descendant: Node<'_>,
9656) -> bool {
9657    cpp_displaced_preprocessor_boundary(conditional)
9658        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
9659}
9660
9661pub fn merge_preprocessor_guards(
9662    left: &HashSet<PreprocessorGuard>,
9663    right: &HashSet<PreprocessorGuard>,
9664) -> Option<HashSet<PreprocessorGuard>> {
9665    let mut merged = left.clone();
9666    for guard in right {
9667        if merged.contains(&guard.negated()) {
9668            return None;
9669        }
9670        merged.insert(guard.clone());
9671    }
9672    Some(merged)
9673}
9674
9675fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
9676    if conditional.kind() == "preproc_ifdef" {
9677        let name = conditional.child_by_field_name("name")?;
9678        let name = node_text(name, source).to_string();
9679        return match conditional.child(0)?.kind() {
9680            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
9681            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
9682            _ => None,
9683        };
9684    }
9685    let condition = conditional.child_by_field_name("condition")?;
9686    simple_preprocessor_expression_guard(condition, source).or_else(|| {
9687        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
9688            node_text(condition, source),
9689        )))
9690    })
9691}
9692
9693fn simple_preprocessor_expression_guard(
9694    expression: Node<'_>,
9695    source: &str,
9696) -> Option<PreprocessorGuard> {
9697    match expression.kind() {
9698        "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
9699            node_text(expression, source).to_string(),
9700        ))),
9701        "number_literal" => match node_text(expression, source).trim() {
9702            "0" => Some(PreprocessorGuard::Constant(false)),
9703            "1" => Some(PreprocessorGuard::Constant(true)),
9704            _ => None,
9705        },
9706        "preproc_defined" => {
9707            let identifier = (0..expression.named_child_count())
9708                .filter_map(|index| expression.named_child(index))
9709                .find(|child| child.kind() == "identifier")?;
9710            Some(PreprocessorGuard::Defined(
9711                node_text(identifier, source).to_string(),
9712            ))
9713        }
9714        "unary_expression"
9715            if expression
9716                .child_by_field_name("operator")
9717                .is_some_and(|operator| operator.kind() == "!") =>
9718        {
9719            simple_preprocessor_expression_guard(
9720                expression.child_by_field_name("argument")?,
9721                source,
9722            )
9723            .map(|guard| guard.negated())
9724        }
9725        "parenthesized_expression" => (0..expression.named_child_count())
9726            .filter_map(|index| expression.named_child(index))
9727            .next()
9728            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
9729        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
9730            expression, source,
9731        ))),
9732        _ => None,
9733    }
9734}
9735
9736fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
9737    match expression.kind() {
9738        "number_literal" => match node_text(expression, source).trim() {
9739            "0" => BooleanGuardExpression::Constant(false),
9740            "1" => BooleanGuardExpression::Constant(true),
9741            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9742                expression, source,
9743            ))),
9744        },
9745        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
9746        "preproc_defined" => {
9747            let identifier = (0..expression.named_child_count())
9748                .filter_map(|index| expression.named_child(index))
9749                .find(|child| child.kind() == "identifier");
9750            identifier.map_or_else(
9751                || {
9752                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9753                        expression, source,
9754                    )))
9755                },
9756                |identifier| {
9757                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
9758                },
9759            )
9760        }
9761        "unary_expression"
9762            if expression
9763                .child_by_field_name("operator")
9764                .is_some_and(|operator| operator.kind() == "!") =>
9765        {
9766            expression.child_by_field_name("argument").map_or_else(
9767                || {
9768                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9769                        expression, source,
9770                    )))
9771                },
9772                |argument| boolean_preprocessor_expression(argument, source).negated(),
9773            )
9774        }
9775        "parenthesized_expression" => (0..expression.named_child_count())
9776            .filter_map(|index| expression.named_child(index))
9777            .next()
9778            .map_or_else(
9779                || {
9780                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9781                        expression, source,
9782                    )))
9783                },
9784                |child| boolean_preprocessor_expression(child, source),
9785            ),
9786        "binary_expression" => {
9787            let operands = || {
9788                Some((
9789                    boolean_preprocessor_expression(
9790                        expression.child_by_field_name("left")?,
9791                        source,
9792                    ),
9793                    boolean_preprocessor_expression(
9794                        expression.child_by_field_name("right")?,
9795                        source,
9796                    ),
9797                ))
9798            };
9799            match expression
9800                .child_by_field_name("operator")
9801                .map(|operator| operator.kind())
9802            {
9803                Some("&&") => operands().map_or_else(
9804                    || {
9805                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9806                            expression, source,
9807                        )))
9808                    },
9809                    |(left, right)| BooleanGuardExpression::all([left, right]),
9810                ),
9811                Some("||") => operands().map_or_else(
9812                    || {
9813                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9814                            expression, source,
9815                        )))
9816                    },
9817                    |(left, right)| BooleanGuardExpression::any([left, right]),
9818                ),
9819                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
9820                    expression, source,
9821                ))),
9822            }
9823        }
9824        _ => {
9825            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
9826        }
9827    }
9828}
9829
9830fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
9831    if targets.len() == 1 {
9832        targets.pop()
9833    } else {
9834        None
9835    }
9836}
9837
9838/// The declaration nodes of `candidate` in `prepared` that stand at a scope a
9839/// later reference can name.
9840///
9841/// A declaration inside a real function body, lambda, or nested block is block
9842/// local and is dropped. A declaration inside a parser-recovery wrapper that
9843/// merely looks callable -- an export macro between `class` and its name, or a
9844/// namespace-opening macro token before `namespace x {` -- keeps class or
9845/// namespace scope and is kept.
9846fn nameable_callable_declaration_nodes<'tree>(
9847    analyzer: &CppGraphSource<'_>,
9848    prepared: &'tree PreparedSyntaxTree,
9849    candidate: &CodeUnit,
9850) -> Vec<Node<'tree>> {
9851    let root = prepared.tree().root_node();
9852    analyzer
9853        .ranges(candidate)
9854        .into_iter()
9855        .filter_map(|range| {
9856            let mut declaration =
9857                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
9858            // A declaration an attribute-like macro invocation swallowed lives
9859            // inside the `ERROR` the parser left, not inside a `declaration`
9860            // node, so that envelope is where the climb stops (#2552).
9861            while !matches!(
9862                declaration.kind(),
9863                "declaration" | "field_declaration" | "function_definition"
9864            ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
9865                declaration,
9866                prepared.source(),
9867            ) {
9868                declaration = declaration.parent()?;
9869            }
9870            let mut ancestor = declaration.parent();
9871            while let Some(node) = ancestor {
9872                if node.kind() == "function_definition"
9873                    && is_recovered_declaration_scope_container(node, prepared.source())
9874                {
9875                    ancestor = node.parent();
9876                    continue;
9877                }
9878                if node.kind() == "compound_statement"
9879                    && node.parent().is_some_and(|parent| {
9880                        is_recovered_declaration_scope_container(parent, prepared.source())
9881                    })
9882                {
9883                    ancestor = node.parent().and_then(|parent| parent.parent());
9884                    continue;
9885                }
9886                if matches!(
9887                    node.kind(),
9888                    "compound_statement" | "function_definition" | "lambda_expression"
9889                ) {
9890                    return None;
9891                }
9892                ancestor = node.parent();
9893            }
9894            Some(declaration)
9895        })
9896        .collect()
9897}
9898
9899fn callable_declaration_activation_in_file(
9900    analyzer: &CppGraphSource<'_>,
9901    prepared: &PreparedSyntaxTree,
9902    candidate: &CodeUnit,
9903    reference: &CallableReferenceContext<'_>,
9904) -> Option<usize> {
9905    nameable_callable_declaration_nodes(analyzer, prepared, candidate)
9906        .into_iter()
9907        .filter(|declaration| {
9908            callable_preprocessor_context_is_visible_for_reference(
9909                *declaration,
9910                prepared.source(),
9911                reference,
9912            )
9913        })
9914        .map(callable_declaration_activation_byte)
9915        .min()
9916}
9917
9918/// C and C++ activate a declared name at the end of its declarator, not at the
9919/// end of the whole declaration. A function definition ends at the closing
9920/// brace of its body, so the declaration end byte would hide the function from
9921/// its own body and make self recursion unresolvable without a prototype.
9922fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
9923    if declaration.kind() != "function_definition" {
9924        return declaration.end_byte();
9925    }
9926    declaration
9927        .child_by_field_name("declarator")
9928        .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
9929}
9930
9931/// The reference side of a callable visibility question.
9932///
9933/// An include-graph walk and a whole-file arity activation ask the question
9934/// without one reference position, so they carry no `position` and therefore no
9935/// guard environment.
9936struct CallableReferenceContext<'a> {
9937    file: &'a ProjectFile,
9938    position: Option<CallableReferencePosition<'a>>,
9939}
9940
9941/// One reference position plus its preprocessor guard environment. The
9942/// environment is computed on demand because most declarations carry no
9943/// non-trivial guard.
9944struct CallableReferencePosition<'a> {
9945    prepared: &'a PreparedSyntaxTree,
9946    byte: usize,
9947    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
9948}
9949
9950impl CallableReferenceContext<'_> {
9951    fn is_c(&self) -> bool {
9952        self.file
9953            .rel_path()
9954            .extension()
9955            .and_then(|extension| extension.to_str())
9956            == Some("c")
9957    }
9958
9959    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
9960        let position = self.position.as_ref()?;
9961        position
9962            .guards
9963            .get_or_init(|| {
9964                position
9965                    .prepared
9966                    .tree()
9967                    .root_node()
9968                    .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
9969                    .and_then(|node| {
9970                        preprocessor_guard_environment(node, position.prepared.source())
9971                    })
9972            })
9973            .as_ref()
9974    }
9975}
9976
9977fn callable_preprocessor_context_is_visible_for_reference(
9978    node: Node<'_>,
9979    source: &str,
9980    reference: &CallableReferenceContext<'_>,
9981) -> bool {
9982    let reference_is_c = reference.is_c();
9983    let mut ancestor = node.parent();
9984    while let Some(conditional) = ancestor {
9985        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
9986            && !is_file_covering_include_guard(conditional, source)
9987            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
9988            && preprocessor_conditional_contains_descendant(conditional, node)
9989        {
9990            let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
9991                return false;
9992            };
9993            match guard {
9994                PreprocessorGuard::Constant(true) => {}
9995                PreprocessorGuard::Constant(false) => return false,
9996                PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
9997                    if reference_is_c {
9998                        return false;
9999                    }
10000                }
10001                PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
10002                    if !reference_is_c {
10003                        return false;
10004                    }
10005                }
10006                // The declaration stands under a guard whose value this
10007                // analyzer cannot decide. It is still co-active with a
10008                // reference whose active guards imply it. Collecting one guard
10009                // per ancestor makes the whole walk a conjunction of the
10010                // declaration requirements.
10011                guard => {
10012                    if !reference
10013                        .guards()
10014                        .is_some_and(|active| preprocessor_guard_holds_at_reference(&guard, active))
10015                    {
10016                        return false;
10017                    }
10018                }
10019            }
10020        }
10021        ancestor = conditional.parent();
10022    }
10023    true
10024}
10025
10026fn flattened_macro_namespace_declaration_matches(
10027    analyzer: &CppGraphSource<'_>,
10028    cpp: &dyn CppSource,
10029    reference_file: &ProjectFile,
10030    visible_declaration: &CodeUnit,
10031    qualified_candidate: &CodeUnit,
10032    reference_byte: usize,
10033) -> bool {
10034    // Namespace-opening macros can leave tree-sitter unable to retain the
10035    // namespace owner after a later recovery point. In that shape the forward
10036    // declaration is indexed at translation-unit scope, while the definition
10037    // still has its qualified owner. Require all surviving structural evidence
10038    // before treating the declaration as activation for that definition.
10039    if visible_declaration.kind() != qualified_candidate.kind()
10040        || visible_declaration.identifier() != qualified_candidate.identifier()
10041        || visible_declaration.signature() != qualified_candidate.signature()
10042        || !visible_declaration.package_name().is_empty()
10043        || qualified_candidate.package_name().is_empty()
10044    {
10045        return false;
10046    }
10047
10048    let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
10049        return false;
10050    };
10051    let root = prepared.tree().root_node();
10052    let closing_brace_limit = if visible_declaration.source() == reference_file {
10053        reference_byte
10054    } else {
10055        usize::MAX
10056    };
10057
10058    analyzer
10059        .ranges(visible_declaration)
10060        .into_iter()
10061        .any(|range| {
10062            let Some(mut declaration) =
10063                root.descendant_for_byte_range(range.start_byte, range.end_byte)
10064            else {
10065                return false;
10066            };
10067            while !matches!(
10068                declaration.kind(),
10069                "declaration" | "field_declaration" | "function_definition"
10070            ) {
10071                let Some(parent) = declaration.parent() else {
10072                    return false;
10073                };
10074                declaration = parent;
10075            }
10076            if declaration
10077                .parent()
10078                .is_none_or(|parent| parent.kind() != "translation_unit")
10079                || !macro_displaced_cpp_return_type(declaration, prepared.source())
10080            {
10081                return false;
10082            }
10083
10084            let mut cursor = root.walk();
10085            root.named_children(&mut cursor).any(|sibling| {
10086                sibling.start_byte() >= declaration.end_byte()
10087                    && sibling.start_byte() < closing_brace_limit
10088                    && direct_unmatched_closing_brace(sibling)
10089            })
10090        })
10091}
10092
10093fn flattened_macro_namespace_components(
10094    declaration: Node<'_>,
10095    source: &str,
10096) -> Option<Vec<String>> {
10097    flattened_macro_function_namespace_components(declaration, source)
10098        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
10099}
10100
10101fn flattened_macro_function_namespace_components(
10102    declaration: Node<'_>,
10103    source: &str,
10104) -> Option<Vec<String>> {
10105    let body = declaration
10106        .parent()
10107        .filter(|parent| parent.kind() == "compound_statement")?;
10108    let function = body.parent()?;
10109    if function.child_by_field_name("body") != Some(body) {
10110        return None;
10111    }
10112    let namespace_name = recovered_macro_namespace_name(function, source)?;
10113    let mut components = enclosing_namespace_components(declaration, source)?;
10114    components.push(namespace_name);
10115    Some(components)
10116}
10117
10118/// The namespace name a namespace-opening macro token displaced into a
10119/// synthetic `function_definition`, or `None` when `function` is not that
10120/// recovery shape.
10121///
10122/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
10123/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
10124/// the macro token, whose declarator is the namespace name behind an `ERROR`
10125/// holding the `namespace` keyword, and whose body spans the whole namespace
10126/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
10127/// artifact from a real function definition.
10128fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
10129    if function.kind() != "function_definition" || !function.has_error() {
10130        return None;
10131    }
10132    let body = function
10133        .child_by_field_name("body")
10134        .filter(|body| body.kind() == "compound_statement")?;
10135    let mut cursor = function.walk();
10136    let prefix = function
10137        .named_children(&mut cursor)
10138        .take_while(|child| child.start_byte() < body.start_byte())
10139        .filter(|child| child.kind() != "comment")
10140        .collect::<Vec<_>>();
10141    let begin_index = prefix.iter().rposition(|child| {
10142        flattened_macro_sentinel_name(*child, source)
10143            .is_some_and(|name| is_namespace_begin_sentinel(&name))
10144    })?;
10145    let mut identifiers = Vec::new();
10146    let mut stack = prefix[begin_index + 1..]
10147        .iter()
10148        .rev()
10149        .copied()
10150        .collect::<Vec<_>>();
10151    while let Some(current) = stack.pop() {
10152        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
10153            identifiers.push(identifier);
10154            continue;
10155        }
10156        let mut cursor = current.walk();
10157        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
10158        stack.extend(children.into_iter().rev());
10159    }
10160    let [keyword, namespace_name] = identifiers.as_slice() else {
10161        return None;
10162    };
10163    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
10164    {
10165        return None;
10166    }
10167    let mut next = function.next_named_sibling();
10168    let next = loop {
10169        let candidate = next?;
10170        next = candidate.next_named_sibling();
10171        if candidate.kind() != "comment" {
10172            break candidate;
10173        }
10174    };
10175    flattened_macro_sentinel_name(next, source)
10176        .is_some_and(|name| is_namespace_end_sentinel(&name))
10177        .then(|| namespace_name.clone())
10178}
10179
10180/// A `function_definition` that exists only because tree-sitter recovered a
10181/// macro-decorated class head or a namespace-opening macro token. A declaration
10182/// in such a body keeps class or namespace scope, so a scope walk must step over
10183/// the wrapper instead of treating the declaration as block local.
10184fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
10185    crate::declarations::is_recovered_exported_class_container(node, source)
10186        || recovered_macro_namespace_name(node, source).is_some()
10187}
10188
10189fn flattened_macro_error_namespace_components(
10190    declaration: Node<'_>,
10191    source: &str,
10192) -> Option<Vec<String>> {
10193    let parent = declaration
10194        .parent()
10195        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
10196    let mut cursor = parent.walk();
10197    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
10198    let declaration_index = siblings
10199        .iter()
10200        .position(|candidate| same_node(*candidate, declaration))?;
10201    let begin_index = (0..declaration_index).rev().find(|index| {
10202        flattened_macro_sentinel_name(siblings[*index], source)
10203            .is_some_and(|name| is_namespace_begin_sentinel(&name))
10204    })?;
10205
10206    let significant = siblings[begin_index + 1..declaration_index]
10207        .iter()
10208        .copied()
10209        .filter(|node| node.kind() != "comment")
10210        .collect::<Vec<_>>();
10211    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
10212        return None;
10213    };
10214    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
10215        return None;
10216    }
10217    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
10218    if significant[2..].iter().any(|node| {
10219        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
10220            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
10221        })
10222    }) {
10223        return None;
10224    }
10225
10226    let mut saw_namespace_close = false;
10227    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
10228        if sibling.kind() == "comment" {
10229            continue;
10230        }
10231        if !saw_namespace_close {
10232            if direct_unmatched_closing_brace(sibling) {
10233                saw_namespace_close = true;
10234                continue;
10235            }
10236            if flattened_macro_sentinel_name(sibling, source).is_some() {
10237                return None;
10238            }
10239            continue;
10240        }
10241        if !flattened_macro_sentinel_name(sibling, source)
10242            .is_some_and(|name| is_namespace_end_sentinel(&name))
10243        {
10244            return None;
10245        }
10246        let mut components = enclosing_namespace_components(declaration, source)?;
10247        components.push(namespace_name);
10248        return Some(components);
10249    }
10250    None
10251}
10252
10253fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
10254    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
10255    // an `expression_statement` with a missing semicolon; inside a namespace
10256    // body the same token stays a bare `type_identifier`.
10257    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
10258        node.named_child(0)?
10259    } else {
10260        node
10261    };
10262    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
10263        node.child_by_field_name("type")
10264            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
10265    })?;
10266    (cpp_export_macro_token(&candidate)
10267        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
10268    .then_some(candidate)
10269}
10270
10271/// Namespace-opening macros are spelled both ways in the wild:
10272/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
10273fn is_namespace_begin_sentinel(name: &str) -> bool {
10274    name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
10275}
10276
10277fn is_namespace_end_sentinel(name: &str) -> bool {
10278    name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
10279}
10280
10281fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
10282    if node.kind() != "ERROR" || node.named_child_count() != 1 {
10283        return None;
10284    }
10285    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
10286    (!cpp_export_macro_token(&name)).then_some(name)
10287}
10288
10289fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
10290    if !matches!(
10291        node.kind(),
10292        "identifier" | "namespace_identifier" | "type_identifier"
10293    ) {
10294        return None;
10295    }
10296    let name = normalize_cpp_whitespace(node_text(node, source));
10297    (!name.is_empty()).then_some(name)
10298}
10299
10300fn guard_requirement_sets_match(
10301    left: &[(usize, HashSet<PreprocessorGuard>)],
10302    right: &[(usize, HashSet<PreprocessorGuard>)],
10303) -> bool {
10304    left.len() == right.len()
10305        && left.iter().all(|(_, left_guards)| {
10306            right
10307                .iter()
10308                .any(|(_, right_guards)| left_guards == right_guards)
10309        })
10310        && right.iter().all(|(_, right_guards)| {
10311            left.iter()
10312                .any(|(_, left_guards)| right_guards == left_guards)
10313        })
10314}
10315
10316fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
10317    let Some(type_node) = declaration.child_by_field_name("type") else {
10318        return false;
10319    };
10320    let type_name = normalize_cpp_whitespace(node_text(type_node, source));
10321    !type_name.is_empty()
10322        && type_name
10323            .chars()
10324            .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
10325        && (0..declaration.named_child_count()).any(|index| {
10326            declaration
10327                .named_child(index)
10328                .is_some_and(|child| child.kind() == "ERROR")
10329        })
10330}
10331
10332fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
10333    node.kind() == "ERROR"
10334        && (0..node.child_count())
10335            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
10336}
10337
10338fn unmatched_closing_brace_is_followed_by_semicolon(node: Node<'_>) -> bool {
10339    let mut following = node.next_named_sibling();
10340    let following = loop {
10341        match following {
10342            Some(candidate) if candidate.kind() == "comment" => {
10343                following = candidate.next_named_sibling();
10344                continue;
10345            }
10346            candidate => break candidate,
10347        }
10348    };
10349    following.is_some_and(|candidate| {
10350        candidate.kind() == "expression_statement"
10351            && candidate.named_child_count() == 0
10352            && (0..candidate.child_count()).any(|index| {
10353                candidate
10354                    .child(index)
10355                    .is_some_and(|child| child.kind() == ";")
10356            })
10357    })
10358}
10359
10360pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
10361    let mut ancestor = node.parent();
10362    while let Some(parent) = ancestor {
10363        if is_preprocessor_conditional(parent)
10364            && !is_file_covering_include_guard(parent, source)
10365            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
10366        {
10367            return false;
10368        }
10369        ancestor = parent.parent();
10370    }
10371    true
10372}
10373
10374fn is_split_cpp_language_linkage_wrapper(
10375    conditional: Node<'_>,
10376    descendant: Node<'_>,
10377    source: &str,
10378) -> bool {
10379    if conditional.child_by_field_name("alternative").is_some()
10380        || !matches!(
10381            simple_preprocessor_guard(conditional, source),
10382            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
10383        )
10384    {
10385        return false;
10386    }
10387    let mut current = descendant.parent();
10388    let linkage = loop {
10389        let Some(node) = current else {
10390            return false;
10391        };
10392        if node == conditional {
10393            return false;
10394        }
10395        if node.kind() == "linkage_specification" {
10396            break node;
10397        }
10398        current = node.parent();
10399    };
10400    if linkage
10401        .child_by_field_name("value")
10402        .is_none_or(|value| node_text(value, source) != "\"C\"")
10403    {
10404        return false;
10405    }
10406    let Some(body) = linkage.child_by_field_name("body") else {
10407        return false;
10408    };
10409    let closes_opening_branch = (0..body.named_child_count())
10410        .filter_map(|index| body.named_child(index))
10411        .take_while(|child| child.end_byte() <= descendant.start_byte())
10412        .any(|child| {
10413            child.kind() == "preproc_call"
10414                && child
10415                    .child_by_field_name("directive")
10416                    .is_some_and(|directive| node_text(directive, source) == "#endif")
10417        });
10418    let reopens_for_closing_brace = (0..body.named_child_count())
10419        .filter_map(|index| body.named_child(index))
10420        .skip_while(|child| child.start_byte() < descendant.end_byte())
10421        .any(|child| {
10422            matches!(
10423                simple_preprocessor_guard(child, source),
10424                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
10425            ) && (0..child.child_count()).any(|index| {
10426                child
10427                    .child(index)
10428                    .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
10429            })
10430        });
10431    closes_opening_branch && reopens_for_closing_brace
10432}
10433
10434/// The argument list a call-shaped node supplies: `f(args)`, `new T(args)`,
10435/// `T{args}` and the member initializer `: field(args)`, whose grammar gives its
10436/// argument list no field name.
10437pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
10438    node.child_by_field_name("arguments")
10439        .or_else(|| node.child_by_field_name("parameters"))
10440        .or_else(|| node.child_by_field_name("value"))
10441        .or_else(|| first_named_child_of_kind(node, "argument_list"))
10442        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
10443}
10444
10445pub fn call_arity(node: Node<'_>) -> usize {
10446    call_arguments_node(node)
10447        .map(|args| argument_children(args).count())
10448        .unwrap_or(0)
10449}
10450
10451pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
10452    let recovered_block_arguments = recovered_block_literal_arguments(node);
10453    (0..node.child_count())
10454        .filter_map(move |index| node.child(index))
10455        .filter(|child| child.is_named() && !child.is_extra())
10456        .flat_map(move |child| {
10457            if let Some((raw, left, right)) = recovered_block_arguments
10458                && child == raw
10459            {
10460                [Some(left), Some(right)]
10461            } else {
10462                [Some(child), None]
10463            }
10464        })
10465        .flatten()
10466}
10467
10468fn recovered_c_keyword_argument_count(
10469    file: &ProjectFile,
10470    call: Node<'_>,
10471    arguments: Node<'_>,
10472    source: &str,
10473) -> usize {
10474    // A C identifier that is a C++ keyword can be displaced twice by the C++
10475    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
10476    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
10477    // the enclosing C function before restoring the otherwise dropped slot.
10478    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
10479        return 0;
10480    }
10481    let mut ancestor = Some(call);
10482    let function = loop {
10483        let Some(current) = ancestor else {
10484            return 0;
10485        };
10486        if current.kind() == "function_definition" {
10487            break current;
10488        }
10489        ancestor = current.parent();
10490    };
10491    let Some(parameters) = function
10492        .child_by_field_name("declarator")
10493        .and_then(|declarator| declarator.child_by_field_name("parameters"))
10494    else {
10495        return 0;
10496    };
10497    let displaced_parameter_keywords = (0..parameters.child_count())
10498        .filter_map(|index| parameters.child(index))
10499        .filter(|error| error.kind() == "ERROR")
10500        .filter_map(|error| {
10501            let parameter = error.prev_named_sibling()?;
10502            if parameter.kind() != "parameter_declaration"
10503                || parameter.end_byte() != error.start_byte()
10504                || extract_variable_name(parameter, source).is_some()
10505            {
10506                return None;
10507            }
10508            let mut children = (0..error.child_count())
10509                .filter_map(|index| error.child(index))
10510                .filter(|child| !child.is_extra() && !child.is_missing());
10511            let keyword = children.next()?;
10512            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
10513                .then_some(keyword)
10514        })
10515        .collect::<Vec<_>>();
10516    if displaced_parameter_keywords.is_empty() {
10517        return 0;
10518    }
10519
10520    (0..arguments.child_count())
10521        .filter_map(|index| arguments.child(index))
10522        .filter(|error| error.kind() == "ERROR" && error.is_extra())
10523        .filter(|error| {
10524            let mut children = (0..error.child_count())
10525                .filter_map(|index| error.child(index))
10526                .filter(|child| !child.is_extra() && !child.is_missing());
10527            let Some(comma) = children.next() else {
10528                return false;
10529            };
10530            let Some(keyword) = children.next() else {
10531                return false;
10532            };
10533            children.next().is_none()
10534                && comma.kind() == ","
10535                && !keyword.is_named()
10536                && keyword.child_count() == 0
10537                && displaced_parameter_keywords
10538                    .iter()
10539                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
10540        })
10541        .count()
10542}
10543
10544fn recovered_block_literal_arguments<'tree>(
10545    arguments: Node<'tree>,
10546) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
10547    if arguments.kind() != "argument_list" {
10548        return None;
10549    }
10550    let mut raw_arguments = (0..arguments.child_count())
10551        .filter_map(|index| arguments.child(index))
10552        .filter(|child| child.is_named() && !child.is_extra());
10553    let raw = raw_arguments.next()?;
10554    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
10555        return None;
10556    }
10557
10558    let left = raw.child_by_field_name("left")?;
10559    if left.is_missing() || left.start_byte() == left.end_byte() {
10560        return None;
10561    }
10562    let right = raw.child_by_field_name("right")?;
10563    if right.kind() != "compound_literal_expression"
10564        || right.is_missing()
10565        || right
10566            .child_by_field_name("type")
10567            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
10568        || right
10569            .child_by_field_name("value")
10570            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
10571    {
10572        return None;
10573    }
10574    let has_intervening_error = (0..raw.child_count())
10575        .filter_map(|index| raw.child(index))
10576        .any(|child| {
10577            child.kind() == "ERROR"
10578                && !child.is_missing()
10579                && child.start_byte() >= left.end_byte()
10580                && child.end_byte() <= right.start_byte()
10581        });
10582    has_intervening_error.then_some((raw, left, right))
10583}
10584
10585pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
10586    match node.kind() {
10587        "new_expression" => node
10588            .child_by_field_name("type")
10589            .or_else(|| node.named_child(0)),
10590        "compound_literal_expression" => node.child_by_field_name("type"),
10591        "call_expression" => node.child_by_field_name("function"),
10592        _ => None,
10593    }
10594}
10595
10596pub fn field_initializer_constructs_target(
10597    node: Node<'_>,
10598    ctx: &ScanCtx<'_>,
10599    owner: &CodeUnit,
10600) -> bool {
10601    // A qualified name in a constructor initializer denotes a base
10602    // subobject constructor (`namespace::Base(args)`), not a member field.  The
10603    // field-initializer grammar exposes the qualified name as one structured
10604    // `qualified_identifier`; resolve its owner through the same lexical type
10605    // machinery used for ordinary C++ type references before considering the
10606    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
10607    // qualified non-constructor member, and an unresolved owner out of the
10608    // target constructor's inverse usage set.
10609    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
10610        return qualified_base_initializer_constructs_target(node, ctx, owner);
10611    }
10612    let Some(name) = node
10613        .child_by_field_name("name")
10614        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
10615        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
10616    else {
10617        return false;
10618    };
10619    let field_name = node_text(name, ctx.source);
10620    ctx.visibility
10621        .visible_identifier_candidates(ctx.file, field_name)
10622        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
10623        .any(|unit| field_declares_type(unit, ctx, owner))
10624}
10625
10626fn qualified_base_initializer_constructs_target(
10627    node: Node<'_>,
10628    ctx: &ScanCtx<'_>,
10629    owner: &CodeUnit,
10630) -> bool {
10631    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
10632        return false;
10633    };
10634    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
10635        return false;
10636    };
10637    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
10638        return false;
10639    };
10640    let resolves_target = |components: &[String]| {
10641        matches!(
10642            ctx.visibility.resolve_type_components_lexically_for_target(
10643                &ctx.analyzer,
10644                ctx.file,
10645                components,
10646                is_globally_qualified_cpp_name(qualified),
10647                &lexical_scope,
10648                owner,
10649            ),
10650            LexicalTypeResolution::Resolved { unit, .. }
10651                if same_visible_symbol(&unit, owner)
10652        )
10653    };
10654    if resolves_target(&components) {
10655        return true;
10656    }
10657
10658    // Some real-world code spells a base mem-initializer as
10659    // `Base::Base(args)`. In that structured path the final component repeats
10660    // the constructor name; resolve the preceding type path. The terminal
10661    // identity check prevents an arbitrary qualified member from taking this
10662    // route.
10663    components
10664        .last()
10665        .is_some_and(|terminal| terminal == owner.identifier())
10666        && resolves_target(&components[..components.len() - 1])
10667}
10668
10669fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
10670    unit.signature()
10671        .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
10672        || ctx
10673            .analyzer
10674            .get_source(unit, false)
10675            .is_some_and(|declaration| {
10676                field_declaration_type_matches(&declaration, unit, ctx, owner)
10677            })
10678}
10679
10680pub fn field_declared_binding(
10681    analyzer: &CppGraphSource<'_>,
10682    visibility: &VisibilityIndex<'_>,
10683    visible_from: &ProjectFile,
10684    field: &CodeUnit,
10685) -> Option<CppScanBinding> {
10686    let fact = visibility.field_declared_type_fact(analyzer, field)?;
10687    let normalized = normalize_field_type_text(&fact.type_text);
10688    let resolved = visibility.resolve_unique_canonical_type_for_declaration(
10689        analyzer,
10690        visible_from,
10691        field,
10692        &normalized,
10693    );
10694    let resolved = match (resolved, fact.template_arguments.as_deref()) {
10695        (Some(primary), Some(arguments)) => visibility
10696            .resolve_template_arguments(visible_from, primary, arguments)
10697            .ok(),
10698        (resolved, None) => resolved,
10699        (None, Some(_)) => None,
10700    };
10701    Some(CppScanBinding::from_type_name(
10702        normalized,
10703        resolved,
10704        fact.indirection,
10705    ))
10706}
10707
10708/// The one logical type the candidates name, or why they do not name one.
10709fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
10710    let Some(first) = candidates.first() else {
10711        return Err(TypeCandidateFailure::Unresolvable);
10712    };
10713    if candidates
10714        .iter()
10715        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
10716    {
10717        Ok((*first).clone())
10718    } else {
10719        Err(TypeCandidateFailure::Ambiguous)
10720    }
10721}
10722
10723fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
10724    logical_type_candidate(candidates).ok()
10725}
10726
10727fn unique_type_candidate_preserving_alias(
10728    analyzer: &CppGraphSource<'_>,
10729    candidates: &[&CodeUnit],
10730) -> Option<CodeUnit> {
10731    let first = *candidates.first()?;
10732    if declared_type_alias(analyzer, first) {
10733        return candidates
10734            .iter()
10735            .all(|candidate| {
10736                declared_type_alias(analyzer, candidate)
10737                    && candidate.kind() == first.kind()
10738                    && candidate.fq_name() == first.fq_name()
10739                    && candidate.source() == first.source()
10740            })
10741            .then(|| first.clone());
10742    }
10743    candidates
10744        .iter()
10745        .all(|candidate| {
10746            !declared_type_alias(analyzer, candidate)
10747                && candidate.kind() == first.kind()
10748                && candidate.fq_name() == first.fq_name()
10749        })
10750        .then(|| first.clone())
10751}
10752
10753fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
10754    is_type_alias(unit)
10755        || analyzer
10756            .type_alias_provider()
10757            .is_some_and(|provider| provider.is_type_alias(unit))
10758}
10759
10760pub fn field_declared_type_binding(
10761    analyzer: &CppGraphSource<'_>,
10762    visibility: &VisibilityIndex<'_>,
10763    visible_from: &ProjectFile,
10764    field: &CodeUnit,
10765) -> Option<(String, Option<CodeUnit>, i32)> {
10766    let fact = visibility.field_declared_type_fact(analyzer, field)?;
10767    let normalized = normalize_field_type_text(&fact.type_text);
10768    let primary = visibility.resolve_unique_canonical_type_for_declaration(
10769        analyzer,
10770        visible_from,
10771        field,
10772        &normalized,
10773    );
10774    let resolved = match (primary, fact.template_arguments.as_deref()) {
10775        (Some(primary), Some(arguments)) => visibility
10776            .resolve_template_arguments(visible_from, primary, arguments)
10777            .ok(),
10778        (resolved, None) => resolved,
10779        (None, Some(_)) => None,
10780    };
10781    Some((normalized, resolved, fact.indirection))
10782}
10783
10784fn decode_field_declared_type_fact(
10785    analyzer: &CppGraphSource<'_>,
10786    field: &CodeUnit,
10787) -> Option<DeclaredFieldTypeFact> {
10788    let declaration = analyzer.get_source(field, false)?;
10789    let mut parser = Parser::new();
10790    parser
10791        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10792        .ok()?;
10793    let tree = parser.parse(&declaration, None)?;
10794    let mut stack = vec![tree.root_node()];
10795    while let Some(node) = stack.pop() {
10796        if matches!(node.kind(), "declaration" | "field_declaration")
10797            && let Some(type_node) = node
10798                .child_by_field_name("type")
10799                .or_else(|| first_type_child(node))
10800            && let Some(indirection) =
10801                declared_name_indirection(node, type_node, field.identifier(), &declaration)
10802        {
10803            let declared_type = if matches!(
10804                type_node.kind(),
10805                "class_specifier" | "struct_specifier" | "union_specifier"
10806            ) {
10807                type_node.child_by_field_name("name")
10808            } else {
10809                Some(type_node)
10810            };
10811            let type_text = declared_type.map_or_else(
10812                || field.identifier().to_string(),
10813                |declared_type| node_text(declared_type, &declaration).to_string(),
10814            );
10815            return Some(DeclaredFieldTypeFact {
10816                type_text,
10817                indirection,
10818                template_arguments: declared_type.and_then(|declared_type| {
10819                    cpp_template_reference_arguments(declared_type, &declaration)
10820                }),
10821            });
10822        }
10823        let mut cursor = node.walk();
10824        stack.extend(node.named_children(&mut cursor));
10825    }
10826    None
10827}
10828
10829/// Text of the type that a C or C++ alias declaration names, read from the
10830/// `type_definition` or `alias_declaration` node's `type` field.
10831///
10832/// The declaration text is never scanned. A function-pointer typedef
10833/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
10834/// so no prefix or suffix of the spelling isolates the target.
10835///
10836/// An alias whose declarator is a function declarator names a function type:
10837/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
10838/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
10839/// so such an alias has no canonical target. Its `type` field holds the return
10840/// type `R`, which is a different type from the alias, so this returns `None`
10841/// rather than that return type.
10842pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
10843    let mut parser = Parser::new();
10844    parser
10845        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10846        .ok()?;
10847    let tree = parser.parse(declaration, None)?;
10848    let mut stack = vec![tree.root_node()];
10849    while let Some(node) = stack.pop() {
10850        let type_node = match node.kind() {
10851            "type_definition" => {
10852                let mut cursor = node.walk();
10853                if node
10854                    .children_by_field_name("declarator", &mut cursor)
10855                    .any(declarator_names_function_type)
10856                {
10857                    return None;
10858                }
10859                node.child_by_field_name("type")?
10860            }
10861            "alias_declaration" => {
10862                let type_node = node.child_by_field_name("type")?;
10863                if type_node
10864                    .child_by_field_name("declarator")
10865                    .is_some_and(declarator_names_function_type)
10866                {
10867                    return None;
10868                }
10869                type_node
10870            }
10871            _ => {
10872                let mut cursor = node.walk();
10873                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
10874                stack.extend(children.into_iter().rev());
10875                continue;
10876            }
10877        };
10878        return Some(node_text(type_node, declaration).to_string());
10879    }
10880    None
10881}
10882
10883/// Whether an alias declaration's own declarator adds indirection that
10884/// [`cpp_alias_declaration_target_text`] does not report.
10885///
10886/// That function reads the declaration's `type` field, where `typedef Foo *Bar`
10887/// keeps only `Foo`: the `*` lives in the sibling declarator. Substituting such
10888/// an alias would equate `f(Bar)` with `f(Foo)`, so a comparison that cannot
10889/// prove the alias adds no indirection must refuse to follow it. A declaration
10890/// this cannot read at all is refused for the same reason.
10891fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
10892    let mut parser = Parser::new();
10893    if parser
10894        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10895        .is_err()
10896    {
10897        return true;
10898    }
10899    let Some(tree) = parser.parse(declaration, None) else {
10900        return true;
10901    };
10902    let mut stack = vec![tree.root_node()];
10903    while let Some(node) = stack.pop() {
10904        let declarators = match node.kind() {
10905            "type_definition" => {
10906                let mut cursor = node.walk();
10907                node.children_by_field_name("declarator", &mut cursor)
10908                    .collect::<Vec<_>>()
10909            }
10910            "alias_declaration" => node
10911                .child_by_field_name("type")
10912                .and_then(|type_node| type_node.child_by_field_name("declarator"))
10913                .into_iter()
10914                .collect::<Vec<_>>(),
10915            _ => {
10916                let mut cursor = node.walk();
10917                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
10918                stack.extend(children.into_iter().rev());
10919                continue;
10920            }
10921        };
10922        return declarators.into_iter().any(cpp_declarator_adds_indirection);
10923    }
10924    true
10925}
10926
10927/// True when an alias declarator names a function type.
10928///
10929/// The declarator chain is walked through the `declarator` field, so the
10930/// parameter list -- a sibling field -- is never entered and a parameter's own
10931/// function declarator cannot be mistaken for the alias's.
10932fn declarator_names_function_type(declarator: Node<'_>) -> bool {
10933    let mut current = Some(declarator);
10934    while let Some(node) = current {
10935        match node.kind() {
10936            "function_declarator" | "abstract_function_declarator" => return true,
10937            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
10938                current = node.named_child(0);
10939            }
10940            _ => current = node.child_by_field_name("declarator"),
10941        }
10942    }
10943    false
10944}
10945
10946/// Whether one indexed field declaration is a function or function-pointer
10947/// value. This follows tree-sitter declarator fields and never infers
10948/// callability from source spelling.
10949pub fn cpp_field_declaration_names_function_type(declaration: &str, field_name: &str) -> bool {
10950    let mut parser = Parser::new();
10951    if parser
10952        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10953        .is_err()
10954    {
10955        return false;
10956    }
10957    let Some(tree) = parser.parse(declaration, None) else {
10958        return false;
10959    };
10960    let mut stack = vec![tree.root_node()];
10961    while let Some(node) = stack.pop() {
10962        if matches!(node.kind(), "declaration" | "field_declaration") {
10963            let mut cursor = node.walk();
10964            if node
10965                .children_by_field_name("declarator", &mut cursor)
10966                .any(|declarator| {
10967                    declarator_name_node(declarator).is_some_and(|name| {
10968                        node_text(name, declaration) == field_name
10969                            && declarator_names_function_type(declarator)
10970                    })
10971                })
10972            {
10973                return true;
10974            }
10975        }
10976        let mut cursor = node.walk();
10977        stack.extend(node.named_children(&mut cursor));
10978    }
10979    false
10980}
10981
10982/// Whether one indexed alias declaration names a function or function-pointer
10983/// type. The alias name is matched through the declarator field so a function
10984/// type used by a parameter cannot be mistaken for the alias itself.
10985pub fn cpp_alias_declaration_names_function_type(declaration: &str, alias_name: &str) -> bool {
10986    let mut parser = Parser::new();
10987    if parser
10988        .set_language(&tree_sitter_cpp::LANGUAGE.into())
10989        .is_err()
10990    {
10991        return false;
10992    }
10993    let Some(tree) = parser.parse(declaration, None) else {
10994        return false;
10995    };
10996    let mut stack = vec![tree.root_node()];
10997    while let Some(node) = stack.pop() {
10998        match node.kind() {
10999            "type_definition" => {
11000                let mut cursor = node.walk();
11001                if node
11002                    .children_by_field_name("declarator", &mut cursor)
11003                    .any(|declarator| {
11004                        extract_typedef_declarator_name(declarator, declaration)
11005                            .is_some_and(|name| name == alias_name)
11006                            && declarator_names_function_type(declarator)
11007                    })
11008                {
11009                    return true;
11010                }
11011            }
11012            "alias_declaration" => {
11013                let names_alias = node
11014                    .child_by_field_name("name")
11015                    .is_some_and(|name| node_text(name, declaration) == alias_name);
11016                if names_alias
11017                    && node
11018                        .child_by_field_name("type")
11019                        .and_then(|type_node| type_node.child_by_field_name("declarator"))
11020                        .is_some_and(declarator_names_function_type)
11021                {
11022                    return true;
11023                }
11024            }
11025            _ => {}
11026        }
11027        let mut cursor = node.walk();
11028        stack.extend(node.named_children(&mut cursor));
11029    }
11030    false
11031}
11032
11033fn decode_structured_alias_target(
11034    analyzer: &CppGraphSource<'_>,
11035    unit: &CodeUnit,
11036) -> Option<StructuredAliasTarget> {
11037    analyzer
11038        .get_source(unit, false)
11039        .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
11040        .or_else(|| {
11041            let signature = unit.signature()?;
11042            decode_structured_alias_target_source(unit, signature, false)
11043        })
11044}
11045
11046fn decode_structured_alias_target_source(
11047    unit: &CodeUnit,
11048    declaration: &str,
11049    require_top_level: bool,
11050) -> Option<StructuredAliasTarget> {
11051    let mut parser = Parser::new();
11052    parser
11053        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11054        .ok()?;
11055    let tree = parser.parse(declaration, None)?;
11056    let mut stack = vec![tree.root_node()];
11057    while let Some(node) = stack.pop() {
11058        let type_node = match node.kind() {
11059            "type_definition" => {
11060                if require_top_level
11061                    && node
11062                        .parent()
11063                        .is_none_or(|parent| parent.kind() != "translation_unit")
11064                {
11065                    let mut cursor = node.walk();
11066                    stack.extend(node.named_children(&mut cursor));
11067                    continue;
11068                }
11069                let mut declarator_cursor = node.walk();
11070                let declarator = node
11071                    .children_by_field_name("declarator", &mut declarator_cursor)
11072                    .find(|declarator| {
11073                        extract_typedef_declarator_name(*declarator, declaration)
11074                            .is_some_and(|name| name == unit.identifier())
11075                    })?;
11076                if declarator_names_function_type(declarator) {
11077                    return None;
11078                }
11079                node.child_by_field_name("type")?
11080            }
11081            "alias_declaration" => {
11082                if require_top_level
11083                    && node
11084                        .parent()
11085                        .is_none_or(|parent| parent.kind() != "translation_unit")
11086                {
11087                    let mut cursor = node.walk();
11088                    stack.extend(node.named_children(&mut cursor));
11089                    continue;
11090                }
11091                let name = node.child_by_field_name("name")?;
11092                if node_text(name, declaration) != unit.identifier() {
11093                    return None;
11094                }
11095                let type_node = node.child_by_field_name("type")?;
11096                if type_node
11097                    .child_by_field_name("declarator")
11098                    .is_some_and(declarator_names_function_type)
11099                {
11100                    return None;
11101                }
11102                type_node
11103            }
11104            _ => {
11105                let mut cursor = node.walk();
11106                stack.extend(node.named_children(&mut cursor));
11107                continue;
11108            }
11109        };
11110        return structured_alias_type_target(type_node, declaration);
11111    }
11112    None
11113}
11114
11115fn structured_alias_type_target(
11116    mut type_node: Node<'_>,
11117    source: &str,
11118) -> Option<StructuredAliasTarget> {
11119    while type_node.kind() == "type_descriptor" {
11120        type_node = type_node.child_by_field_name("type")?;
11121    }
11122    if type_node.kind() == "primitive_type" {
11123        return Some(StructuredAliasTarget::Builtin);
11124    }
11125    if matches!(
11126        type_node.kind(),
11127        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
11128    ) {
11129        type_node = type_node.child_by_field_name("name")?;
11130    }
11131    let global = type_node.child_by_field_name("scope").is_none()
11132        && type_node.child(0).is_some_and(|child| child.kind() == "::");
11133    let mut components = Vec::new();
11134    append_structured_type_components(type_node, source, &mut components)?;
11135    let arguments = cpp_template_reference_arguments(type_node, source);
11136    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
11137        components,
11138        global,
11139        arguments,
11140    })
11141}
11142
11143fn append_structured_type_components(
11144    node: Node<'_>,
11145    source: &str,
11146    out: &mut Vec<String>,
11147) -> Option<()> {
11148    match node.kind() {
11149        "identifier" | "namespace_identifier" | "type_identifier" => {
11150            out.push(node_text(node, source).to_string());
11151            Some(())
11152        }
11153        "template_type" => {
11154            append_structured_type_components(node.child_by_field_name("name")?, source, out)
11155        }
11156        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
11157            if let Some(scope) = node.child_by_field_name("scope") {
11158                append_structured_type_components(scope, source, out)?;
11159            }
11160            append_structured_type_components(node.child_by_field_name("name")?, source, out)
11161        }
11162        _ => None,
11163    }
11164}
11165
11166fn declared_name_indirection(
11167    declaration: Node<'_>,
11168    type_node: Node<'_>,
11169    field_name: &str,
11170    source: &str,
11171) -> Option<i32> {
11172    let mut stack = Vec::new();
11173    let mut cursor = declaration.walk();
11174    stack.extend(
11175        declaration
11176            .named_children(&mut cursor)
11177            .filter(|child| !same_node(*child, type_node)),
11178    );
11179    while let Some(node) = stack.pop() {
11180        if matches!(node.kind(), "identifier" | "field_identifier")
11181            && node_text(node, source) == field_name
11182        {
11183            let mut indirection = 0;
11184            let mut current = node.parent();
11185            while let Some(parent) = current {
11186                if same_node(parent, declaration) {
11187                    return Some(indirection);
11188                }
11189                if parent.kind() == "pointer_declarator" {
11190                    indirection += 1;
11191                }
11192                current = parent.parent();
11193            }
11194            return None;
11195        }
11196        let mut cursor = node.walk();
11197        stack.extend(node.named_children(&mut cursor));
11198    }
11199    None
11200}
11201
11202fn field_declaration_type_matches(
11203    declaration: &str,
11204    unit: &CodeUnit,
11205    ctx: &ScanCtx<'_>,
11206    owner: &CodeUnit,
11207) -> bool {
11208    ctx.visibility
11209        .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
11210        || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
11211            let normalized = normalize_field_type_text(type_text);
11212            ctx.visibility
11213                .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
11214                || ctx.visibility.resolves_to_type(
11215                    &ctx.analyzer,
11216                    ctx.file,
11217                    normalized.as_str(),
11218                    owner,
11219                )
11220        })
11221}
11222
11223fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
11224    let declaration = declaration
11225        .split(['=', ';'])
11226        .next()
11227        .unwrap_or(declaration)
11228        .trim();
11229    let index = declaration.rfind(field_name)?;
11230    let before = &declaration[..index];
11231    let after = &declaration[index + field_name.len()..];
11232    if before.chars().next_back().is_some_and(is_identifier_char)
11233        || after.chars().next().is_some_and(is_identifier_char)
11234    {
11235        return None;
11236    }
11237    Some(before.trim())
11238}
11239
11240fn normalize_field_type_text(type_text: &str) -> String {
11241    const FIELD_SPECIFIERS: [&str; 8] = [
11242        "extern ",
11243        "static ",
11244        "mutable ",
11245        "constexpr ",
11246        "constinit ",
11247        "inline ",
11248        "volatile ",
11249        "const ",
11250    ];
11251
11252    let mut normalized = normalize_type_text(type_text);
11253    loop {
11254        let Some(stripped) = FIELD_SPECIFIERS
11255            .iter()
11256            .find_map(|specifier| normalized.strip_prefix(specifier))
11257        else {
11258            return normalized;
11259        };
11260        normalized = normalize_type_text(stripped);
11261    }
11262}
11263
11264fn is_identifier_char(ch: char) -> bool {
11265    ch == '_' || ch.is_ascii_alphanumeric()
11266}
11267
11268pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
11269    let Some(type_node) = node.child_by_field_name("type") else {
11270        return false;
11271    };
11272    ctx.visibility.resolves_to_type(
11273        &ctx.analyzer,
11274        ctx.file,
11275        node_text(type_node, ctx.source),
11276        owner,
11277    )
11278}
11279
11280pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
11281    !ctx.analyzer
11282        .declarations(ctx.file)
11283        .into_iter()
11284        .filter(|unit| unit.is_function())
11285        .any(|unit| {
11286            ctx.analyzer.ranges(&unit).iter().any(|range| {
11287                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
11288            })
11289        })
11290}
11291
11292/// How a `T var ...;` declaration initializes its object.
11293pub enum DeclarationConstructorInitializer<'tree> {
11294    /// Direct initialization, `T var(args)` or `T var{args}`: the argument list
11295    /// the declaration hands the constructor.
11296    Arguments(Node<'tree>),
11297    /// Copy initialization from one expression, `T var = expr`, which supplies a
11298    /// single constructor argument without spelling an argument list.
11299    Expression(Node<'tree>),
11300    /// `T var;`, which names no constructor argument at all.
11301    Empty,
11302}
11303
11304pub fn declaration_constructor_initializer(
11305    node: Node<'_>,
11306) -> DeclarationConstructorInitializer<'_> {
11307    let mut cursor = node.walk();
11308    for child in node.named_children(&mut cursor) {
11309        if child.kind() == "init_declarator" {
11310            let Some(value) = child
11311                .child_by_field_name("value")
11312                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
11313                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
11314            else {
11315                return DeclarationConstructorInitializer::Empty;
11316            };
11317            return match value.kind() {
11318                "argument_list" | "initializer_list" => {
11319                    DeclarationConstructorInitializer::Arguments(value)
11320                }
11321                "compound_literal_expression" => call_arguments_node(value)
11322                    .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
11323                        DeclarationConstructorInitializer::Arguments(arguments)
11324                    }),
11325                _ => DeclarationConstructorInitializer::Expression(value),
11326            };
11327        }
11328        if is_declarator_node(child) {
11329            return declarator_parameters(child)
11330                .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
11331                    DeclarationConstructorInitializer::Arguments(parameters)
11332                });
11333        }
11334    }
11335    DeclarationConstructorInitializer::Empty
11336}
11337
11338pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
11339    match declaration_constructor_initializer(node) {
11340        DeclarationConstructorInitializer::Arguments(arguments) => {
11341            argument_children(arguments).count()
11342        }
11343        DeclarationConstructorInitializer::Expression(_) => 1,
11344        DeclarationConstructorInitializer::Empty => 0,
11345    }
11346}
11347
11348/// The parameter list of the innermost declarator, which is where a
11349/// `T var(args)` declaration parsed as a function declarator keeps the
11350/// constructor arguments.
11351fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
11352    let mut current = node;
11353    loop {
11354        if let Some(parameters) = current.child_by_field_name("parameters") {
11355            return Some(parameters);
11356        }
11357        current = current.child_by_field_name("declarator")?;
11358    }
11359}
11360
11361pub(super) fn first_named_child_of_kind<'tree>(
11362    node: Node<'tree>,
11363    kind: &str,
11364) -> Option<Node<'tree>> {
11365    let mut cursor = node.walk();
11366    node.named_children(&mut cursor)
11367        .find(|child| child.kind() == kind)
11368}
11369
11370fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
11371    let mut stack = vec![root];
11372    while let Some(node) = stack.pop() {
11373        if node.kind() == kind {
11374            return Some(node);
11375        }
11376        for index in (0..node.named_child_count()).rev() {
11377            if let Some(child) = node.named_child(index) {
11378                stack.push(child);
11379            }
11380        }
11381    }
11382    None
11383}
11384
11385fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
11386    if node.kind() == "identifier" {
11387        return true;
11388    }
11389    if node.kind() == "parenthesized_expression" {
11390        return false;
11391    }
11392    if node.kind() == "call_expression" {
11393        return node
11394            .child_by_field_name("function")
11395            .is_some_and(|function| function.kind() == "identifier");
11396    }
11397    let mut stack = vec![node];
11398    while let Some(descendant) = stack.pop() {
11399        if descendant != node && descendant.kind() == "parenthesized_expression" {
11400            continue;
11401        }
11402        if descendant.kind() == "identifier" {
11403            return true;
11404        }
11405        if descendant.kind() == "call_expression" {
11406            if descendant
11407                .child_by_field_name("function")
11408                .is_some_and(|function| function.kind() == "identifier")
11409            {
11410                return true;
11411            }
11412            continue;
11413        }
11414        for index in (0..descendant.named_child_count()).rev() {
11415            if let Some(child) = descendant.named_child(index) {
11416                stack.push(child);
11417            }
11418        }
11419    }
11420    false
11421}
11422
11423fn macro_expansion_shape_is_safe(
11424    node: Node<'_>,
11425    source: &str,
11426    parameters: &[String],
11427    environment: &MacroEnvironment,
11428) -> bool {
11429    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
11430        return true;
11431    }
11432    if node.kind() == "call_expression" {
11433        let Some(function) = node.child_by_field_name("function") else {
11434            return true;
11435        };
11436        if function.kind() != "identifier" {
11437            return true;
11438        }
11439        let function_name = node_text(function, source);
11440        if parameters
11441            .iter()
11442            .any(|parameter| parameter == function_name)
11443        {
11444            return false;
11445        }
11446        if !environment.may_bind(function_name) {
11447            return true;
11448        }
11449        let Some(arguments) = node.child_by_field_name("arguments") else {
11450            return false;
11451        };
11452        return argument_children(arguments).all(|argument| {
11453            if argument.kind() == "identifier"
11454                && parameters
11455                    .iter()
11456                    .any(|parameter| parameter == node_text(argument, source))
11457            {
11458                return false;
11459            }
11460            macro_expansion_shape_is_safe(argument, source, parameters, environment)
11461        });
11462    }
11463    let mut stack = vec![node];
11464    while let Some(descendant) = stack.pop() {
11465        if descendant != node {
11466            if descendant.kind() == "parenthesized_expression" {
11467                continue;
11468            }
11469            if descendant.kind() == "call_expression" {
11470                let expands = descendant
11471                    .child_by_field_name("function")
11472                    .filter(|function| function.kind() == "identifier")
11473                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
11474                if expands {
11475                    return false;
11476                }
11477                continue;
11478            }
11479        }
11480        if descendant.kind() == "identifier" {
11481            let identifier = node_text(descendant, source);
11482            if parameters.iter().any(|parameter| parameter == identifier)
11483                || environment.may_bind(identifier)
11484            {
11485                return false;
11486            }
11487        }
11488        for index in (0..descendant.named_child_count()).rev() {
11489            if let Some(child) = descendant.named_child(index) {
11490                stack.push(child);
11491            }
11492        }
11493    }
11494    true
11495}
11496
11497fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
11498    let text = node_text(path, source);
11499    match path.kind() {
11500        "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
11501        "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
11502        _ => None,
11503    }
11504}
11505
11506fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
11507    let descendant = node;
11508    while let Some(parent) = node.parent() {
11509        if is_preprocessor_conditional(parent)
11510            && !is_file_covering_include_guard(parent, source)
11511            && preprocessor_conditional_contains_descendant(parent, descendant)
11512        {
11513            return true;
11514        }
11515        node = parent;
11516    }
11517    false
11518}
11519
11520fn is_preprocessor_conditional(node: Node<'_>) -> bool {
11521    matches!(
11522        node.kind(),
11523        "preproc_if"
11524            | "preproc_ifdef"
11525            | "preproc_ifndef"
11526            | "preproc_elif"
11527            | "preproc_elifdef"
11528            | "preproc_else"
11529    )
11530}
11531
11532fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
11533    node.parent()
11534        .filter(|parent| parent.kind() == "translation_unit")
11535        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
11536        && is_canonical_include_guard(node, source)
11537}
11538
11539fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
11540    if node.kind() != "preproc_ifdef"
11541        || node
11542            .child(0)
11543            .is_none_or(|directive| directive.kind() != "#ifndef")
11544        || node.child_by_field_name("alternative").is_some()
11545    {
11546        return false;
11547    }
11548    let Some(guard_name) = node.child_by_field_name("name") else {
11549        return false;
11550    };
11551    let mut cursor = node.walk();
11552    node.named_children(&mut cursor)
11553        .find(|child| *child != guard_name && child.kind() != "comment")
11554        .filter(|child| child.kind() == "preproc_def")
11555        .and_then(|definition| definition.child_by_field_name("name"))
11556        .is_some_and(|defined_name| {
11557            node_text(defined_name, source) == node_text(guard_name, source)
11558        })
11559}
11560
11561fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
11562    let mut guard = None;
11563    for index in 0..root.named_child_count() {
11564        let Some(child) = root.named_child(index) else {
11565            continue;
11566        };
11567        if child.kind() == "comment" || is_pragma_once(child, source) {
11568            continue;
11569        }
11570        if guard.is_none() && is_canonical_include_guard(child, source) {
11571            guard = Some(child);
11572        } else {
11573            return None;
11574        }
11575    }
11576    guard
11577        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
11578        .map(|name| node_text(name, source).to_string())
11579}
11580
11581fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
11582    if (0..root.named_child_count())
11583        .filter_map(|index| root.named_child(index))
11584        .any(|child| is_pragma_once(child, source))
11585    {
11586        return MacroIncludeProtection::PragmaOnce;
11587    }
11588    top_level_canonical_include_guard_name(root, source)
11589        .map(MacroIncludeProtection::MacroGuard)
11590        .unwrap_or(MacroIncludeProtection::None)
11591}
11592
11593fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
11594    node.kind() == "preproc_call"
11595        && node
11596            .child_by_field_name("directive")
11597            .is_some_and(|directive| node_text(directive, source) == "#pragma")
11598        && node
11599            .child_by_field_name("argument")
11600            .is_some_and(|argument| node_text(argument, source).trim() == "once")
11601}
11602
11603fn parse_preproc_identifier(argument: &str) -> Option<String> {
11604    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
11605    let mut parser = Parser::new();
11606    parser
11607        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11608        .ok()?;
11609    let tree = parser.parse(&sentinel, None)?;
11610    if tree.root_node().has_error() {
11611        return None;
11612    }
11613    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
11614    let identifier = statement.named_child(0)?;
11615    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
11616        .then(|| node_text(identifier, &sentinel).to_string())
11617}
11618
11619pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
11620    match node.kind() {
11621        "identifier" | "field_identifier" => {
11622            let name = node_text(node, source).trim();
11623            (!name.is_empty()).then(|| name.to_string())
11624        }
11625        "abstract_array_declarator"
11626        | "abstract_function_declarator"
11627        | "abstract_parenthesized_declarator"
11628        | "abstract_pointer_declarator"
11629        | "abstract_reference_declarator" => None,
11630        "function_declarator" => node
11631            .child_by_field_name("declarator")
11632            .or_else(|| node.child_by_field_name("name"))
11633            .and_then(|child| extract_variable_name(child, source)),
11634        _ => node
11635            .child_by_field_name("declarator")
11636            .or_else(|| node.child_by_field_name("name"))
11637            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
11638            .and_then(|child| extract_variable_name(child, source)),
11639    }
11640}
11641
11642/// Whether `file` is proven to use plain-C source semantics.
11643///
11644/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
11645/// compilation dialect on their own, so only an exact `.c` source extension is
11646/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
11647/// identifiers.
11648///
11649/// The exact-lowercase-`.c` rule itself lives in [`LanguageDialect::for_path`],
11650/// which extraction reads too (a `.c` file is extracted with C tag scope), so
11651/// the doctrine has exactly one definition.
11652pub fn is_c_source_file(file: &ProjectFile) -> bool {
11653    LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
11654}
11655
11656/// Whether tree-sitter parsed the operand of C `sizeof(T)` as an expression
11657/// identifier even though `T` may denote a typedef.
11658///
11659/// The grammar cannot distinguish `sizeof(value)` from `sizeof(Type)` without
11660/// semantic information. Keep this helper structural and narrow; callers must
11661/// still prove a visible type and reject an active ordinary-namespace shadow.
11662pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
11663    if !is_c_source_file(file) || node.kind() != "identifier" {
11664        return false;
11665    }
11666    let mut operand = node;
11667    while let Some(parent) = operand.parent().filter(|parent| {
11668        parent.kind() == "parenthesized_expression"
11669            && parent.named_child_count() == 1
11670            && parent.named_child(0) == Some(operand)
11671    }) {
11672        operand = parent;
11673    }
11674    operand.parent().is_some_and(|parent| {
11675        parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
11676    })
11677}
11678
11679/// Whether `node` is a template argument name that tree-sitter spelled with
11680/// type syntax.
11681///
11682/// The grammar cannot tell a type argument from a non-type (value) argument, so
11683/// it gives both the same shape:
11684/// `template_argument_list -> type_descriptor -> type_identifier`. In
11685/// `std::array<W, N>` the type `W` and the constant `N` parse identically, and
11686/// so do `std::span<const uint8_t, ED448_LEN>`'s length and a nested type
11687/// member used as a real type argument.
11688///
11689/// This helper reports only the syntactic position. A caller must still prove
11690/// which namespace explains the spelling: forward navigation asks the type
11691/// namespace first and reads the leaf as a value only when no type explains it,
11692/// and the inverse field scan admits the leaf only when no visible type does
11693/// (#2556).
11694pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
11695    if node.kind() != "type_identifier" {
11696        return false;
11697    }
11698    let Some(descriptor) = node
11699        .parent()
11700        .filter(|parent| parent.kind() == "type_descriptor")
11701    else {
11702        return false;
11703    };
11704    if descriptor.child_by_field_name("type") != Some(node) {
11705        return false;
11706    }
11707    let Some(arguments) = descriptor
11708        .parent()
11709        .filter(|parent| parent.kind() == "template_argument_list")
11710    else {
11711        return false;
11712    };
11713    arguments.parent().is_some_and(|owner| {
11714        matches!(
11715            owner.kind(),
11716            "template_type" | "template_function" | "template_method"
11717        ) && owner.child_by_field_name("arguments") == Some(arguments)
11718    })
11719}
11720
11721/// Whether a reference written in `file` reads C++ source with C semantics.
11722///
11723/// [`is_c_source_file`] answers the half a path settles on its own. The other
11724/// half is a header, which has no dialect of its own: it is read as C exactly
11725/// when every workspace translation unit that provably compiles it compiles it
11726/// as C ([`CppSource::header_uses_c_semantics`], issue #1970).
11727///
11728/// This is the gate for anything that is really about the compilation
11729/// language of the code being read -- which reading of an included header's
11730/// declarations is in scope, whether `this` is an ordinary identifier. It is
11731/// NOT the gate for a question that is genuinely about a `.c` file on disk;
11732/// those keep calling [`is_c_source_file`].
11733pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
11734    is_c_source_file(file) || cpp.header_uses_c_semantics(file)
11735}
11736
11737pub fn is_declarator_node(node: Node<'_>) -> bool {
11738    matches!(
11739        node.kind(),
11740        "identifier"
11741            | "field_identifier"
11742            | "pointer_declarator"
11743            | "reference_declarator"
11744            | "array_declarator"
11745            | "parenthesized_declarator"
11746            | "function_declarator"
11747    )
11748}
11749
11750#[derive(Clone, Default)]
11751pub struct OrphanedNamespaceTypeScopeIndex {
11752    scopes: Vec<OrphanedNamespaceTypeScope>,
11753}
11754
11755#[derive(Clone)]
11756struct OrphanedNamespaceTypeScope {
11757    body_end: usize,
11758    scope_end: usize,
11759    components: Vec<String>,
11760}
11761
11762impl OrphanedNamespaceTypeScopeIndex {
11763    /// Index the physical namespace interval that remains after tree-sitter
11764    /// prematurely closes an error-marked namespace at a recovered class body.
11765    /// The later unmatched `}` is the structured upper bound. A nested damaged
11766    /// namespace can lose that token to its still-open enclosing namespace; in
11767    /// that shape the enclosing namespace body's end is the tighter surviving
11768    /// bound. Declarations after either bound do not enter the recovered scope.
11769    pub fn build(root: Node<'_>, source: &str) -> Self {
11770        let mut scopes = Vec::new();
11771        let mut stack = vec![root];
11772        while let Some(current) = stack.pop() {
11773            if current.kind() == "namespace_definition"
11774                && current.has_error()
11775                && let Some(body) = current.child_by_field_name("body")
11776                && current.end_byte() == body.end_byte()
11777                && let Some(name) = current.child_by_field_name("name")
11778            {
11779                let mut components =
11780                    enclosing_namespace_components(current, source).unwrap_or_default();
11781                if append_cpp_name_components(name, source, &mut components).is_some()
11782                    && !components.is_empty()
11783                {
11784                    let mut scope_end = None;
11785                    let mut following = current.next_named_sibling();
11786                    while let Some(candidate) = following {
11787                        if direct_unmatched_closing_brace(candidate)
11788                            && !unmatched_closing_brace_is_followed_by_semicolon(candidate)
11789                        {
11790                            scope_end = Some(candidate.start_byte());
11791                            break;
11792                        }
11793                        following = candidate.next_named_sibling();
11794                    }
11795                    let scope_end = scope_end.or_else(|| {
11796                        std::iter::successors(current.parent(), |ancestor| ancestor.parent())
11797                            .filter(|ancestor| ancestor.kind() == "namespace_definition")
11798                            .filter_map(|ancestor| ancestor.child_by_field_name("body"))
11799                            .map(|body| body.end_byte())
11800                            .find(|end| *end > body.end_byte())
11801                    });
11802                    if let Some(scope_end) = scope_end {
11803                        scopes.push(OrphanedNamespaceTypeScope {
11804                            body_end: body.end_byte(),
11805                            scope_end,
11806                            components,
11807                        });
11808                    }
11809                }
11810            }
11811            if !current.has_error() {
11812                continue;
11813            }
11814            let mut cursor = current.walk();
11815            stack.extend(
11816                current
11817                    .named_children(&mut cursor)
11818                    .filter(|child| child.has_error()),
11819            );
11820        }
11821        Self { scopes }
11822    }
11823
11824    pub fn scope_at(&self, byte: usize) -> Option<(usize, &[String])> {
11825        self.scopes
11826            .iter()
11827            .filter(|scope| scope.body_end < byte && byte < scope.scope_end)
11828            .max_by_key(|scope| (scope.components.len(), scope.body_end))
11829            .map(|scope| (scope.body_end, scope.components.as_slice()))
11830    }
11831}
11832
11833#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11834pub enum RecoveredDeclaratorTypeContext {
11835    Declaration,
11836    FunctionDefinition,
11837    Parameter,
11838}
11839
11840/// Recognize a real type displaced into a qualified declarator by parser
11841/// recovery.
11842///
11843/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
11844/// type and `Result` were the scope of a qualified declarator with a missing
11845/// `::`. A template return such as `API Result<T> make()` uses a
11846/// `template_type` for the same recovered scope. The same recovery occurs for
11847/// macro-prefixed definitions, extern variables, and macro-decorated
11848/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
11849/// the macro). Keep this intentionally structural: the recovered scope must
11850/// have the grammar's missing separator, the qualified node must occupy the
11851/// declaration's declarator chain, a separate nonempty type must occupy the
11852/// normal type field, and the recovered name must unwrap to a real declarator
11853/// name.
11854pub fn recovered_macro_decorated_declarator_type(
11855    node: Node<'_>,
11856) -> Option<RecoveredDeclaratorTypeContext> {
11857    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
11858}
11859
11860/// Return the declaration/function `type` displaced by a macro-shaped
11861/// qualified declarator, together with the enclosing declaration context.
11862/// Callers use the macro scope only as structural admission evidence; the
11863/// returned node is the real type reference to resolve and record.
11864pub fn recovered_macro_decorated_type_node(
11865    node: Node<'_>,
11866) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
11867    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
11868        return None;
11869    }
11870    let qualified = node.parent()?;
11871    if qualified.kind() != "qualified_identifier"
11872        || qualified.child_by_field_name("scope") != Some(node)
11873        || !(0..qualified.child_count())
11874            .filter_map(|index| qualified.child(index))
11875            .any(|child| child.kind() == "::" && child.is_missing())
11876    {
11877        return None;
11878    }
11879    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
11880        return None;
11881    }
11882
11883    let (declaration, context) = recovered_declarator_container(qualified)?;
11884    let type_node = declaration
11885        .child_by_field_name("type")
11886        .filter(|type_node| {
11887            *type_node != qualified
11888                && !type_node.is_missing()
11889                && type_node.start_byte() != type_node.end_byte()
11890        })?;
11891    Some((type_node, context))
11892}
11893
11894fn recovered_declarator_container(
11895    mut declarator: Node<'_>,
11896) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
11897    loop {
11898        let parent = declarator.parent()?;
11899        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
11900            return Some((
11901                parent
11902                    .parent()
11903                    .filter(|declaration| declaration.kind() == "declaration")?,
11904                RecoveredDeclaratorTypeContext::Declaration,
11905            ));
11906        }
11907        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
11908            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
11909        }
11910        if parent.kind() == "function_definition"
11911            && has_field_child(parent, "declarator", declarator)
11912        {
11913            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
11914        }
11915        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
11916        // level down: the parameter's `type` field takes the macro token and
11917        // the real type `T` becomes the recovered scope of the declarator.
11918        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
11919        // candidate at all (#1830).
11920        if matches!(
11921            parent.kind(),
11922            "parameter_declaration" | "optional_parameter_declaration"
11923        ) && has_field_child(parent, "declarator", declarator)
11924        {
11925            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
11926        }
11927        if !matches!(
11928            parent.kind(),
11929            "array_declarator"
11930                | "function_declarator"
11931                | "parenthesized_declarator"
11932                | "pointer_declarator"
11933                | "pointer_type_declarator"
11934                | "reference_declarator"
11935        ) || !has_field_child(parent, "declarator", declarator)
11936        {
11937            return None;
11938        }
11939        declarator = parent;
11940    }
11941}
11942
11943fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
11944    let mut cursor = parent.walk();
11945    parent
11946        .children_by_field_name(field, &mut cursor)
11947        .any(|child| child == target)
11948}
11949
11950fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
11951    loop {
11952        if node.is_missing() || node.start_byte() == node.end_byte() {
11953            return false;
11954        }
11955        match node.kind() {
11956            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
11957                return true;
11958            }
11959            "array_declarator"
11960            | "function_declarator"
11961            | "parenthesized_declarator"
11962            | "pointer_declarator"
11963            | "pointer_type_declarator"
11964            | "reference_declarator" => {
11965                let Some(declarator) = node.child_by_field_name("declarator") else {
11966                    return false;
11967                };
11968                node = declarator;
11969            }
11970            _ => return false,
11971        }
11972    }
11973}
11974
11975/// Aggregate-owner proof for a structurally recognized designated initializer.
11976pub enum DesignatedInitializerOwner {
11977    Resolved(CodeUnit),
11978    Unresolved,
11979}
11980
11981/// Recognize a designated-initializer field and, when possible, resolve its
11982/// aggregate owner.
11983///
11984/// Covers both the grammar's ordinary `field_designator` shape and the exact
11985/// recovery used for `.field = value` after a preprocessor-split array
11986/// initializer. Nested aggregate levels are deliberately left unresolved unless
11987/// the single outer level is the containing array initializer: resolving those
11988/// would require following the enclosing field's declared type. `None` means the
11989/// node is not a designator at all; an unresolved designator remains classified so
11990/// callers cannot fall through to unrelated global/member heuristics.
11991pub fn designated_initializer_owner(
11992    visibility: &VisibilityIndex<'_>,
11993    file: &ProjectFile,
11994    source: &str,
11995    node: Node<'_>,
11996) -> Option<DesignatedInitializerOwner> {
11997    if let Some(designator) = node
11998        .parent()
11999        .filter(|parent| parent.kind() == "field_designator")
12000    {
12001        let pair = designator.parent()?;
12002        if pair.kind() != "initializer_pair"
12003            || pair.child_by_field_name("designator") != Some(designator)
12004        {
12005            return None;
12006        }
12007        let initializer = pair.parent()?;
12008        if initializer.kind() != "initializer_list" {
12009            return None;
12010        }
12011        return Some(classified_designated_owner(initializer_list_owner(
12012            visibility,
12013            file,
12014            source,
12015            initializer,
12016        )));
12017    }
12018
12019    let init_declarator = node.parent()?;
12020    if init_declarator.child_by_field_name("declarator") != Some(node)
12021        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
12022    {
12023        return None;
12024    }
12025    Some(classified_designated_owner(declaration_owner(
12026        visibility,
12027        file,
12028        source,
12029        init_declarator.parent()?,
12030    )))
12031}
12032
12033fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
12034    owner.map_or(
12035        DesignatedInitializerOwner::Unresolved,
12036        DesignatedInitializerOwner::Resolved,
12037    )
12038}
12039
12040fn initializer_list_owner(
12041    visibility: &VisibilityIndex<'_>,
12042    file: &ProjectFile,
12043    source: &str,
12044    initializer: Node<'_>,
12045) -> Option<CodeUnit> {
12046    let mut current = initializer;
12047    let mut outer_initializer_lists = 0usize;
12048    loop {
12049        let parent = current.parent()?;
12050        match parent.kind() {
12051            "initializer_pair" => return None,
12052            "initializer_list" => {
12053                outer_initializer_lists += 1;
12054                if outer_initializer_lists > 1 {
12055                    return None;
12056                }
12057                current = parent;
12058            }
12059            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
12060                let declaration = parent.parent()?;
12061                if outer_initializer_lists == 1
12062                    && !parent
12063                        .child_by_field_name("declarator")
12064                        .is_some_and(contains_array_declarator)
12065                {
12066                    return None;
12067                }
12068                return declaration_owner(visibility, file, source, declaration);
12069            }
12070            "compound_literal_expression"
12071                if parent.child_by_field_name("value") == Some(current)
12072                    && outer_initializer_lists == 0 =>
12073            {
12074                let type_node = parent.child_by_field_name("type")?;
12075                return resolve_designated_owner_type(visibility, file, source, type_node);
12076            }
12077            "ERROR" => current = parent,
12078            _ => return None,
12079        }
12080    }
12081}
12082
12083fn declaration_owner(
12084    visibility: &VisibilityIndex<'_>,
12085    file: &ProjectFile,
12086    source: &str,
12087    declaration: Node<'_>,
12088) -> Option<CodeUnit> {
12089    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
12090        return None;
12091    }
12092    let type_node = declaration
12093        .child_by_field_name("type")
12094        .or_else(|| first_type_child(declaration))?;
12095    resolve_designated_owner_type(visibility, file, source, type_node)
12096}
12097
12098fn resolve_designated_owner_type(
12099    visibility: &VisibilityIndex<'_>,
12100    file: &ProjectFile,
12101    source: &str,
12102    type_node: Node<'_>,
12103) -> Option<CodeUnit> {
12104    let type_name = normalize_type_text(node_text(type_node, source));
12105    visibility
12106        .resolve_type(file, &type_name)
12107        .filter(CodeUnit::is_class)
12108}
12109
12110fn contains_array_declarator(declarator: Node<'_>) -> bool {
12111    let mut stack = vec![declarator];
12112    while let Some(node) = stack.pop() {
12113        if node.kind() == "array_declarator" {
12114            return true;
12115        }
12116        if matches!(node.kind(), "initializer_list" | "compound_statement") {
12117            continue;
12118        }
12119        let mut cursor = node.walk();
12120        stack.extend(node.named_children(&mut cursor));
12121    }
12122    false
12123}
12124
12125pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
12126    let mut cursor = node.walk();
12127    node.named_children(&mut cursor).find(|child| {
12128        matches!(
12129            child.kind(),
12130            "type_identifier"
12131                | "primitive_type"
12132                | "qualified_identifier"
12133                | "scoped_type_identifier"
12134                | "struct_specifier"
12135                | "union_specifier"
12136                | "enum_specifier"
12137        )
12138    })
12139}
12140
12141pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
12142    visibility: &VisibilityIndex<'_>,
12143    file: &ProjectFile,
12144    source: &str,
12145    declarator: Node<'_>,
12146    type_text: Option<&str>,
12147    bindings: &LocalInferenceEngine<T>,
12148) -> bool {
12149    if !has_ancestor_kind(declarator, "compound_statement") {
12150        return false;
12151    }
12152    if declarator
12153        .child_by_field_name("declarator")
12154        .is_none_or(|declarator| declarator.kind() != "identifier")
12155    {
12156        return false;
12157    }
12158    if !type_text
12159        .and_then(|text| visibility.resolve_type(file, text))
12160        .is_some_and(|unit| unit.is_class())
12161    {
12162        return false;
12163    }
12164    declarator
12165        .child_by_field_name("parameters")
12166        .is_some_and(|parameters| {
12167            constructor_parameters_look_like_expressions(parameters, source, bindings)
12168        })
12169}
12170
12171fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
12172    parameters: Node<'_>,
12173    source: &str,
12174    bindings: &LocalInferenceEngine<T>,
12175) -> bool {
12176    let mut cursor = parameters.walk();
12177    parameters.named_children(&mut cursor).any(|parameter| {
12178        !matches!(
12179            parameter.kind(),
12180            "parameter_declaration" | "optional_parameter_declaration"
12181        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
12182    })
12183}
12184
12185fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
12186    parameter: Node<'_>,
12187    source: &str,
12188    bindings: &LocalInferenceEngine<T>,
12189) -> bool {
12190    let text = node_text(parameter, source).trim();
12191    if text
12192        .chars()
12193        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
12194        && bindings.is_shadowed(text)
12195    {
12196        return true;
12197    }
12198
12199    let Some(base) = parameter
12200        .child_by_field_name("type")
12201        .filter(|base| base.kind() == "type_identifier")
12202    else {
12203        return false;
12204    };
12205    let Some(subscript) = parameter
12206        .child_by_field_name("declarator")
12207        .filter(|declarator| declarator.kind() == "abstract_array_declarator")
12208    else {
12209        return false;
12210    };
12211    subscript.child_by_field_name("size").is_some()
12212        && bindings.is_shadowed(node_text(base, source).trim())
12213}
12214
12215pub fn is_declaration_name(node: Node<'_>) -> bool {
12216    let Some(parent) = node.parent() else {
12217        return false;
12218    };
12219    if parent
12220        .child_by_field_name("name")
12221        .is_some_and(|name| same_node(name, node))
12222    {
12223        if matches!(
12224            parent.kind(),
12225            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
12226        ) {
12227            return cpp_tag_specifier_declares_name(parent);
12228        }
12229        if matches!(
12230            parent.kind(),
12231            "namespace_definition"
12232                | "namespace_alias_definition"
12233                | "alias_declaration"
12234                | "enumerator"
12235        ) {
12236            return true;
12237        }
12238    }
12239
12240    let mut current = Some(parent);
12241    while let Some(ancestor) = current {
12242        let type_definition = ancestor.kind() == "type_definition";
12243        let mut declarator_cursor = ancestor.walk();
12244        if ancestor
12245            .children_by_field_name("declarator", &mut declarator_cursor)
12246            .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
12247        {
12248            return true;
12249        }
12250        if matches!(
12251            ancestor.kind(),
12252            "declaration"
12253                | "field_declaration"
12254                | "parameter_declaration"
12255                | "optional_parameter_declaration"
12256                | "function_definition"
12257                | "type_definition"
12258                | "alias_declaration"
12259                | "class_specifier"
12260                | "struct_specifier"
12261                | "union_specifier"
12262                | "enum_specifier"
12263        ) {
12264            return false;
12265        }
12266        current = ancestor.parent();
12267    }
12268    false
12269}
12270
12271/// Whether tree-sitter recovered a qualified friend-class type as an ordinary
12272/// declaration's declarator inside a malformed class body.
12273///
12274/// An export macro between `class` and the class name can make the containing
12275/// body parse as a function body. A source declaration such as
12276/// `friend class internal::Friend;` then retains this exact structure:
12277/// `declaration(type: friend, ERROR(class), declarator: internal::Friend)`.
12278/// The declarator is a type reference despite its field role.
12279pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
12280    if !matches!(
12281        node.kind(),
12282        "qualified_identifier" | "scoped_type_identifier"
12283    ) {
12284        return false;
12285    }
12286    let Some(declaration) = node
12287        .parent()
12288        .filter(|parent| parent.kind() == "declaration")
12289    else {
12290        return false;
12291    };
12292    if declaration.child_by_field_name("declarator") != Some(node)
12293        || !declaration
12294            .child_by_field_name("type")
12295            .is_some_and(|friend| {
12296                friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
12297            })
12298    {
12299        return false;
12300    }
12301    let mut cursor = declaration.walk();
12302    let mut errors = declaration
12303        .named_children(&mut cursor)
12304        .filter(|child| child.kind() == "ERROR");
12305    let Some(error) = errors.next() else {
12306        return false;
12307    };
12308    errors.next().is_none()
12309        && error.named_child_count() == 1
12310        && error.named_child(0).is_some_and(|class| {
12311            class.kind() == "identifier" && node_text(class, source) == "class"
12312        })
12313}
12314
12315pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
12316    if !matches!(node.kind(), "identifier" | "field_identifier") || is_declaration_name(node) {
12317        return false;
12318    }
12319    if let Some(parent) = node.parent() {
12320        if parent.kind() == "call_expression"
12321            && parent.child_by_field_name("function") == Some(node)
12322        {
12323            return false;
12324        }
12325        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
12326            && parent.child_by_field_name("label") == Some(node)
12327        {
12328            return false;
12329        }
12330    }
12331    let mut current = node.parent();
12332    while let Some(ancestor) = current {
12333        match ancestor.kind() {
12334            "preproc_ifdef" | "preproc_ifndef" => {
12335                if ancestor
12336                    .child_by_field_name("name")
12337                    .is_some_and(|name| node_range_contains(name, node))
12338                {
12339                    return false;
12340                }
12341            }
12342            "preproc_if" | "preproc_elif" => {
12343                if ancestor
12344                    .child_by_field_name("condition")
12345                    .is_some_and(|condition| node_range_contains(condition, node))
12346                {
12347                    return false;
12348                }
12349            }
12350            "preproc_else" => {}
12351            kind if kind.starts_with("preproc_") => return false,
12352            _ => {}
12353        }
12354        if matches!(
12355            ancestor.kind(),
12356            "translation_unit" | "function_definition" | "compound_statement"
12357        ) {
12358            break;
12359        }
12360        current = ancestor.parent();
12361    }
12362    true
12363}
12364
12365fn node_range_contains(outer: Node<'_>, inner: Node<'_>) -> bool {
12366    outer.start_byte() <= inner.start_byte() && inner.end_byte() <= outer.end_byte()
12367}
12368
12369fn recovered_c_reference_node(
12370    visibility: &VisibilityIndex<'_>,
12371    file: &ProjectFile,
12372    node: Node<'_>,
12373    source: &str,
12374) -> bool {
12375    if node.start_byte() >= node.end_byte()
12376        || node.is_error()
12377        || node.is_missing()
12378        || !matches!(
12379            node.kind(),
12380            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
12381        )
12382        || recovered_c_macro_binding_role(node)
12383        || recovered_c_label_role(node)
12384    {
12385        return false;
12386    }
12387
12388    let name = node_text(node, source);
12389    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
12390        return true;
12391    }
12392    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
12393        return true;
12394    }
12395    if is_declaration_name(node) {
12396        return false;
12397    }
12398    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
12399        return true;
12400    }
12401    recovered_c_reference_anchor(node)
12402}
12403
12404fn recovered_c_explicit_assignment_callee(
12405    visibility: &VisibilityIndex<'_>,
12406    file: &ProjectFile,
12407    node: Node<'_>,
12408    name: &str,
12409) -> bool {
12410    let mut current = node;
12411    let error = loop {
12412        let Some(parent) = current.parent() else {
12413            return false;
12414        };
12415        if parent.is_error() {
12416            break parent;
12417        }
12418        current = parent;
12419    };
12420    let mut cursor = error.walk();
12421    let explicit_recovery_precedes_callee = error
12422        .named_children(&mut cursor)
12423        .take_while(|child| child.start_byte() < node.start_byte())
12424        .any(|child| child.kind() == "explicit_function_specifier");
12425    if !explicit_recovery_precedes_callee {
12426        return false;
12427    }
12428    visibility
12429        .cpp
12430        .declarations(file)
12431        .iter()
12432        .chain(visibility.visible_by_file.get(file).into_iter().flatten())
12433        .any(|candidate| candidate.identifier() == name && candidate.is_function())
12434}
12435
12436fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
12437    while let Some(parent) = node.parent() {
12438        if matches!(
12439            parent.kind(),
12440            "preproc_def" | "preproc_function_def" | "preproc_params"
12441        ) {
12442            return true;
12443        }
12444        if parent.is_error()
12445            || matches!(
12446                parent.kind(),
12447                "translation_unit" | "function_definition" | "compound_statement"
12448            )
12449        {
12450            return false;
12451        }
12452        node = parent;
12453    }
12454    false
12455}
12456
12457fn recovered_c_label_role(node: Node<'_>) -> bool {
12458    node.parent().is_some_and(|parent| {
12459        matches!(parent.kind(), "labeled_statement" | "goto_statement")
12460            && parent.child_by_field_name("label") == Some(node)
12461    })
12462}
12463
12464fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
12465    while let Some(parent) = node.parent() {
12466        if parent.is_error() {
12467            return false;
12468        }
12469        if parent.kind().ends_with("_expression")
12470            || matches!(
12471                parent.kind(),
12472                "argument_list"
12473                    | "return_statement"
12474                    | "expression_statement"
12475                    | "case_statement"
12476                    | "initializer_list"
12477                    | "init_declarator"
12478                    | "array_declarator"
12479                    | "field_designator"
12480                    | "enumerator"
12481            )
12482        {
12483            return true;
12484        }
12485        if matches!(
12486            parent.kind(),
12487            "translation_unit"
12488                | "function_definition"
12489                | "compound_statement"
12490                | "declaration"
12491                | "field_declaration"
12492                | "parameter_declaration"
12493        ) {
12494            return false;
12495        }
12496        node = parent;
12497    }
12498    false
12499}
12500
12501/// Whether a parameter declaration belongs to the callable scope whose body can
12502/// contain references to it.
12503///
12504/// Error recovery can wrap a macro-decorated class body in a synthetic outer
12505/// `function_definition`. Merely finding any callable ancestor would then leak
12506/// parameters from member prototypes into later member bodies. Require the
12507/// parameter to be inside that definition's own declarator instead.
12508pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
12509    let mut current = parameter.parent();
12510    while let Some(ancestor) = current {
12511        if ancestor.kind() == "lambda_expression" {
12512            return ancestor
12513                .child_by_field_name("declarator")
12514                .is_some_and(|declarator| {
12515                    declarator.start_byte() <= parameter.start_byte()
12516                        && parameter.end_byte() <= declarator.end_byte()
12517                });
12518        }
12519        if ancestor.kind() == "function_definition" {
12520            return ancestor
12521                .child_by_field_name("declarator")
12522                .is_some_and(|declarator| {
12523                    declarator.start_byte() <= parameter.start_byte()
12524                        && parameter.end_byte() <= declarator.end_byte()
12525                });
12526        }
12527        current = ancestor.parent();
12528    }
12529    false
12530}
12531
12532pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
12533    let mut current = node.parent();
12534    while let Some(ancestor) = current {
12535        if matches!(
12536            ancestor.kind(),
12537            "parameter_declaration" | "optional_parameter_declaration"
12538        ) {
12539            return ancestor
12540                .child_by_field_name("type")
12541                .is_some_and(|type_node| {
12542                    type_node.start_byte() <= node.start_byte()
12543                        && node.end_byte() <= type_node.end_byte()
12544                });
12545        }
12546        if matches!(
12547            ancestor.kind(),
12548            "function_definition" | "lambda_expression" | "compound_statement"
12549        ) {
12550            return false;
12551        }
12552        current = ancestor.parent();
12553    }
12554    false
12555}
12556
12557fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
12558    if specifier.child_by_field_name("body").is_some() {
12559        return true;
12560    }
12561    let mut current = specifier.parent();
12562    while let Some(ancestor) = current {
12563        match ancestor.kind() {
12564            "type_descriptor"
12565            | "parameter_declaration"
12566            | "optional_parameter_declaration"
12567            | "template_argument_list"
12568            | "cast_expression" => return false,
12569            "declaration" | "field_declaration" => {
12570                let mut cursor = ancestor.walk();
12571                return ancestor
12572                    .children_by_field_name("declarator", &mut cursor)
12573                    .next()
12574                    .is_none();
12575            }
12576            "translation_unit" => return true,
12577            _ => current = ancestor.parent(),
12578        }
12579    }
12580    false
12581}
12582
12583pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
12584    match node.kind() {
12585        "identifier"
12586        | "field_identifier"
12587        | "qualified_identifier"
12588        | "scoped_identifier"
12589        | "operator_name"
12590        | "destructor_name"
12591        | "literal_operator_name" => Some(node),
12592        "reference_declarator" | "parenthesized_declarator" => {
12593            node.named_child(0).and_then(declarator_name_node)
12594        }
12595        _ => node
12596            .child_by_field_name("declarator")
12597            .or_else(|| node.child_by_field_name("name"))
12598            .or_else(|| node.child_by_field_name("field"))
12599            .and_then(declarator_name_node),
12600    }
12601}
12602
12603fn declarator_name_path_contains(
12604    declarator: Node<'_>,
12605    candidate: Node<'_>,
12606    allow_type_identifier: bool,
12607) -> bool {
12608    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
12609        return false;
12610    };
12611    let mut current = Some(declarator);
12612    while let Some(node) = current {
12613        if same_node(node, candidate) {
12614            return true;
12615        }
12616        if same_node(node, name) {
12617            return false;
12618        }
12619        current = node
12620            .child_by_field_name("declarator")
12621            .or_else(|| node.child_by_field_name("name"))
12622            .or_else(|| node.child_by_field_name("field"));
12623    }
12624    false
12625}
12626
12627fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
12628    match node.kind() {
12629        "identifier"
12630        | "field_identifier"
12631        | "operator_name"
12632        | "destructor_name"
12633        | "literal_operator_name" => Some(node),
12634        "type_identifier" if allow_type_identifier => Some(node),
12635        _ => node
12636            .child_by_field_name("declarator")
12637            .or_else(|| node.child_by_field_name("name"))
12638            .or_else(|| node.child_by_field_name("field"))
12639            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
12640    }
12641}
12642
12643/// True when `node` is a component of a larger structured type node whose outer
12644/// range is the single reference surfaced to callers.
12645pub fn is_nested_type_node(node: Node<'_>) -> bool {
12646    node.parent().is_some_and(|parent| {
12647        matches!(
12648            parent.kind(),
12649            "qualified_identifier" | "scoped_type_identifier" | "template_type"
12650        )
12651    })
12652}
12653
12654pub struct OutOfLineMemberDefinitionOwners<'tree> {
12655    pub owners: Vec<(Node<'tree>, CodeUnit)>,
12656    innermost: Option<(Node<'tree>, CodeUnit)>,
12657}
12658
12659impl OutOfLineMemberDefinitionOwners<'_> {
12660    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
12661        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
12662    }
12663}
12664
12665pub struct QualifiedOwnerComponents<'tree> {
12666    pub nodes: Vec<Node<'tree>>,
12667    pub names: Vec<String>,
12668    pub global: bool,
12669}
12670
12671/// True when each structured qualifier on the callable-name path has a real
12672/// `::` token. A macro-prefixed return type can make tree-sitter insert a
12673/// zero-width missing separator and parse `TYPE Result<T> method()` as the
12674/// false qualified declarator `Result<T>::method`.
12675pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
12676    let mut stack = vec![node];
12677    let mut found_separator = false;
12678    while let Some(current) = stack.pop() {
12679        if !matches!(
12680            current.kind(),
12681            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
12682        ) {
12683            continue;
12684        }
12685        let mut current_has_separator = false;
12686        for index in 0..current.child_count() {
12687            let Some(child) = current.child(index) else {
12688                continue;
12689            };
12690            if child.kind() == "::" {
12691                if child.is_missing() {
12692                    return false;
12693                }
12694                current_has_separator = true;
12695                found_separator = true;
12696            }
12697        }
12698        if !current_has_separator {
12699            return false;
12700        }
12701        for field in ["scope", "name"] {
12702            if let Some(child) = current.child_by_field_name(field)
12703                && matches!(
12704                    child.kind(),
12705                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
12706                )
12707            {
12708                stack.push(child);
12709            }
12710        }
12711    }
12712    found_separator
12713}
12714
12715pub fn qualified_owner_components<'tree>(
12716    node: Node<'tree>,
12717    source: &str,
12718) -> Option<QualifiedOwnerComponents<'tree>> {
12719    if !qualified_name_has_concrete_scope_separators(node) {
12720        return None;
12721    }
12722    let mut nodes = cpp_name_component_nodes(node)?;
12723    nodes.pop()?;
12724    if nodes.is_empty() {
12725        return None;
12726    }
12727    let names = nodes
12728        .iter()
12729        .map(|component| node_text(*component, source).to_string())
12730        .collect();
12731    Some(QualifiedOwnerComponents {
12732        nodes,
12733        names,
12734        global: is_globally_qualified_cpp_name(node),
12735    })
12736}
12737
12738pub fn out_of_line_member_definition_owner<'tree>(
12739    analyzer: &CppGraphSource<'_>,
12740    visibility: &VisibilityIndex<'_>,
12741    file: &ProjectFile,
12742    source: &str,
12743    node: Node<'tree>,
12744) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
12745    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
12746        || !has_ancestor_kind(node, "function_definition")
12747        || !is_function_declarator_name_root(node)
12748    {
12749        return None;
12750    }
12751    let qualified = qualified_owner_components(node, source)?;
12752    let lexical_scope = enclosing_namespace_components(node, source)?;
12753    let mut owners = Vec::new();
12754    let mut innermost = None;
12755
12756    for component_count in 1..=qualified.names.len() {
12757        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
12758            .resolve_type_components_lexically(
12759                analyzer,
12760                file,
12761                &qualified.names[..component_count],
12762                qualified.global,
12763                &lexical_scope,
12764            )
12765            && !owners
12766                .iter()
12767                .any(|(_, existing)| same_visible_symbol(existing, &unit))
12768        {
12769            if component_count == qualified.names.len() {
12770                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
12771            }
12772            owners.push((qualified.nodes[component_count - 1], unit));
12773        }
12774    }
12775
12776    // The C++ analyzer has already reconciled an indexed out-of-line callable
12777    // against the include-visible class table. Consult that canonical owner
12778    // chain only when ordinary lexical lookup could not recover the innermost
12779    // owner.  A one-segment qualifier is safe here only when the enclosing
12780    // indexed callable has an authoritative class owner and the parser's
12781    // namespace path is a (possibly sparse) subsequence of that owner path.
12782    // The latter is what lets macro-wrapped namespace sentinels recover a
12783    // missing `time_internal`/`cord_internal` component without guessing an
12784    // unrelated short name.
12785    if innermost.is_none() {
12786        let indexed_owner_components = visibility
12787            .indexed_enclosing_owner_scope(analyzer, file, node)
12788            .or_else(|| {
12789                // Retain the legacy rendered-name fallback for the existing
12790                // multi-segment path when an enclosing owner chain is not
12791                // available (for example, cache-loaded units without parent
12792                // links).  One-segment recovery must stay canonical-only.
12793                if qualified.names.len() <= 1 {
12794                    return None;
12795                }
12796                let range = Range {
12797                    start_byte: node.start_byte(),
12798                    end_byte: node.end_byte(),
12799                    start_line: node.start_position().row,
12800                    end_line: node.end_position().row,
12801                };
12802                let start = analyzer.enclosing_code_unit(file, &range)?;
12803                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12804                    brokk_bifrost_core::analyzer::Language::Cpp,
12805                    &cpp_name_for(&start),
12806                );
12807                components.pop();
12808                Some(components)
12809            });
12810        if let Some(indexed_owner_components) = indexed_owner_components
12811            && indexed_owner_components.len() > qualified.names.len()
12812            && indexed_owner_components.ends_with(&qualified.names)
12813            && indexed_namespace_path_is_recoverable(
12814                &lexical_scope,
12815                &indexed_owner_components,
12816                qualified.names.len(),
12817            )
12818            // A globally-qualified one-segment owner is an explicit request
12819            // for the top-level binding; do not reinterpret it as a missing
12820            // namespace component.  Existing multi-segment global lookups
12821            // retain their historical indexed recovery.
12822            && (qualified.names.len() > 1 || !qualified.global)
12823        {
12824            let namespace_count = indexed_owner_components.len() - qualified.names.len();
12825            for component_count in 1..=qualified.names.len() {
12826                let expected = &indexed_owner_components[..namespace_count + component_count];
12827                let owner_node = qualified.nodes[component_count - 1];
12828                for owner in visibility
12829                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
12830                    .filter(|candidate| candidate.is_class())
12831                    .filter(|candidate| {
12832                        canonical_cpp_scope_components(candidate) == expected
12833                            && visibility.external_type_candidate_visible_in_context(
12834                                analyzer, file, candidate, node,
12835                            )
12836                    })
12837                {
12838                    if component_count == qualified.names.len() && innermost.is_none() {
12839                        innermost = Some((owner_node, owner.clone()));
12840                    }
12841                    if !owners
12842                        .iter()
12843                        .any(|(_, existing)| same_symbol(existing, owner))
12844                    {
12845                        owners.push((owner_node, owner.clone()));
12846                    }
12847                }
12848            }
12849        }
12850    }
12851    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
12852}
12853
12854fn is_function_declarator_name_root(node: Node<'_>) -> bool {
12855    let mut current = node;
12856    while let Some(parent) = current.parent() {
12857        if parent.kind() == "function_declarator" {
12858            return parent.child_by_field_name("declarator") == Some(current);
12859        }
12860        if matches!(
12861            parent.kind(),
12862            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
12863        ) && parent.child_by_field_name("declarator") == Some(current)
12864        {
12865            current = parent;
12866            continue;
12867        }
12868        return false;
12869    }
12870    false
12871}
12872
12873pub fn append_cpp_name_components(
12874    node: Node<'_>,
12875    source: &str,
12876    out: &mut Vec<String>,
12877) -> Option<()> {
12878    out.extend(
12879        cpp_name_component_nodes(node)?
12880            .into_iter()
12881            .map(|component| node_text(component, source).to_string()),
12882    );
12883    Some(())
12884}
12885
12886pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
12887    let mut components = Vec::new();
12888    append_cpp_name_components(node, source, &mut components)?;
12889    Some(components)
12890}
12891
12892/// Resolve a structured type spelling from an object-like macro replacement
12893/// when definition-site source order has no answer.
12894///
12895/// Macro replacement tokens are looked up where the macro is expanded, so a
12896/// type declared later in the defining header can still be their destination.
12897/// Without expanding every invocation, accept only one include-visible logical
12898/// class or alias whose structured path ends in the replacement components.
12899/// An ordinary lexical answer always takes precedence at the call site.
12900pub fn unique_macro_replacement_type_candidate(
12901    analyzer: &CppGraphSource<'_>,
12902    visibility: &VisibilityIndex<'_>,
12903    file: &ProjectFile,
12904    components: &[String],
12905) -> Option<CodeUnit> {
12906    let terminal = components.last()?;
12907    let mut candidates = Vec::new();
12908    for candidate in visibility
12909        .visible_identifier_candidates(file, terminal)
12910        .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
12911        .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
12912    {
12913        if !candidates
12914            .iter()
12915            .any(|existing| same_logical_symbol(existing, candidate))
12916        {
12917            candidates.push(candidate.clone());
12918        }
12919    }
12920    (candidates.len() == 1).then(|| candidates.remove(0))
12921}
12922
12923/// The base scopes named by member using-declarations for `member` in one
12924/// class source range.
12925///
12926/// The grammar supplies the qualified identifier and each component. Keep
12927/// this interpretation shared between forward overload lookup and inverse
12928/// owner routing rather than reparsing a rendered `Base::member` string at
12929/// either call site.
12930pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
12931    let mut parser = Parser::new();
12932    if parser
12933        .set_language(&tree_sitter_cpp::LANGUAGE.into())
12934        .is_err()
12935    {
12936        return Vec::new();
12937    }
12938    let Some(tree) = parser.parse(source, None) else {
12939        return Vec::new();
12940    };
12941    let mut scopes = Vec::new();
12942    let mut pending = vec![tree.root_node()];
12943    while let Some(node) = pending.pop() {
12944        if node.kind() == "using_declaration" {
12945            let Some(imported) = node.named_child(0) else {
12946                continue;
12947            };
12948            let Some(mut components) = cpp_type_name_components(imported, source) else {
12949                continue;
12950            };
12951            if components.pop().as_deref() == Some(member) && !components.is_empty() {
12952                scopes.push(components.join("::"));
12953            }
12954            continue;
12955        }
12956        for index in (0..node.named_child_count()).rev() {
12957            if let Some(child) = node.named_child(index) {
12958                pending.push(child);
12959            }
12960        }
12961    }
12962    scopes
12963}
12964
12965/// Whether a structured using-declaration scope can name `qualified` as an
12966/// ancestor class. The boundary check prevents `Base` from matching
12967/// `OtherBase` while allowing a relative `Base` spelling to match `ns::Base`.
12968pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
12969    qualified == scope
12970        || qualified
12971            .strip_suffix(scope)
12972            .is_some_and(|prefix| prefix.ends_with("::"))
12973}
12974
12975/// Whether `node` is the direct structured type payload of a template
12976/// argument. This role remains meaningful even when a surrounding expression
12977/// is below tree-sitter recovery, because both the `template_argument_list`
12978/// and the `type_descriptor` retain their named fields.
12979pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
12980    let Some(type_descriptor) = node.parent() else {
12981        return false;
12982    };
12983    if type_descriptor.kind() != "type_descriptor"
12984        || type_descriptor.child_by_field_name("type") != Some(node)
12985    {
12986        return false;
12987    }
12988    let Some(arguments) = type_descriptor.parent() else {
12989        return false;
12990    };
12991    if arguments.kind() != "template_argument_list" {
12992        return false;
12993    }
12994    arguments.parent().is_some_and(|parent| {
12995        matches!(parent.kind(), "template_type" | "template_function")
12996            && parent.child_by_field_name("arguments") == Some(arguments)
12997    })
12998}
12999
13000pub fn cpp_template_reference_arguments(
13001    mut node: Node<'_>,
13002    source: &str,
13003) -> Option<Vec<CppTemplateExpression>> {
13004    loop {
13005        match node.kind() {
13006            "template_type" | "template_function" => {
13007                let arguments = node.child_by_field_name("arguments")?;
13008                let mut cursor = arguments.walk();
13009                return Some(
13010                    arguments
13011                        .named_children(&mut cursor)
13012                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
13013                        .map(|argument| CppTemplateExpression {
13014                            text: normalize_cpp_whitespace(node_text(argument, source)),
13015                            // One template term from a resolver query; see `ParentIndex::unindexed`.
13016                            term: cpp_template_term(
13017                                argument,
13018                                source,
13019                                &[],
13020                                &ParentIndex::unindexed(),
13021                            ),
13022                        })
13023                        .collect(),
13024                );
13025            }
13026            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
13027                node = node
13028                    .child_by_field_name("name")
13029                    .or_else(|| node.child_by_field_name("type"))?;
13030            }
13031            _ => return None,
13032        }
13033    }
13034}
13035
13036fn cpp_reconcile_primary_template_parameters(
13037    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
13038    preferred: &CodeUnit,
13039) -> Option<Vec<CppTemplateParameterMetadata>> {
13040    let canonical = candidates
13041        .iter()
13042        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
13043    let mut merged = canonical
13044        .parameters
13045        .iter()
13046        .map(|parameter| CppTemplateParameterMetadata {
13047            name: parameter.name.clone(),
13048            kind: parameter.kind,
13049            variadic: parameter.variadic,
13050            default: None,
13051        })
13052        .collect::<Vec<_>>();
13053
13054    for (_, metadata) in candidates {
13055        if metadata.parameters.len() != merged.len() {
13056            return None;
13057        }
13058        let rename_bindings = metadata
13059            .parameters
13060            .iter()
13061            .zip(&merged)
13062            .map(|(parameter, canonical)| {
13063                (
13064                    parameter.name.clone(),
13065                    CppTemplateTerm::Parameter(canonical.name.clone()),
13066                )
13067            })
13068            .collect::<HashMap<_, _>>();
13069        for ((parameter, canonical), merged_parameter) in metadata
13070            .parameters
13071            .iter()
13072            .zip(&canonical.parameters)
13073            .zip(&mut merged)
13074        {
13075            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
13076                return None;
13077            }
13078            let Some(default) = &parameter.default else {
13079                continue;
13080            };
13081            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
13082            if let Some(existing) = &merged_parameter.default {
13083                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
13084                    return None;
13085                }
13086            } else {
13087                merged_parameter.default = Some(CppTemplateExpression {
13088                    text: default.text.clone(),
13089                    term: normalized_term,
13090                });
13091            }
13092        }
13093    }
13094    Some(merged)
13095}
13096
13097pub fn cpp_bind_template_arguments(
13098    parameters: &[CppTemplateParameterMetadata],
13099    explicit_arguments: &[CppTemplateExpression],
13100) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
13101    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
13102    if variadic_index.is_some_and(|index| {
13103        index + 1 != parameters.len()
13104            || parameters[index + 1..]
13105                .iter()
13106                .any(|parameter| parameter.variadic)
13107    }) {
13108        return None;
13109    }
13110    let fixed_count = variadic_index.unwrap_or(parameters.len());
13111    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
13112        return None;
13113    }
13114    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
13115    let mut expanded = explicit_arguments[..explicit_fixed_count]
13116        .iter()
13117        .map(cpp_clone_template_expression_iterative)
13118        .collect::<Vec<_>>();
13119    let mut bindings = HashMap::default();
13120    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
13121        bindings.insert(
13122            parameter.name.clone(),
13123            cpp_clone_template_term_iterative(&argument.term),
13124        );
13125    }
13126    for parameter in &parameters[explicit_fixed_count..fixed_count] {
13127        let default = parameter.default.as_ref()?;
13128        let term = cpp_substitute_template_term(&default.term, &bindings)?;
13129        bindings.insert(parameter.name.clone(), term.clone());
13130        expanded.push(CppTemplateExpression {
13131            text: default.text.clone(),
13132            term,
13133        });
13134    }
13135    if let Some(index) = variadic_index {
13136        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
13137        expanded.extend(
13138            packed_arguments
13139                .iter()
13140                .map(cpp_clone_template_expression_iterative),
13141        );
13142        bindings.insert(
13143            parameters[index].name.clone(),
13144            CppTemplateTerm::Node {
13145                kind: "parameter_pack".to_string(),
13146                children: packed_arguments
13147                    .iter()
13148                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
13149                    .collect(),
13150            },
13151        );
13152    }
13153    Some((expanded, bindings))
13154}
13155
13156fn cpp_specialization_matches(
13157    metadata: &CppTemplateMetadata,
13158    arguments: &[CppTemplateExpression],
13159) -> bool {
13160    if metadata.specialization_arguments.len() != arguments.len() {
13161        return false;
13162    }
13163    let parameter_names = metadata
13164        .parameters
13165        .iter()
13166        .map(|parameter| parameter.name.as_str())
13167        .collect::<HashSet<_>>();
13168    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
13169    for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
13170        if !cpp_unify_template_term(
13171            &pattern.term,
13172            &argument.term,
13173            &parameter_names,
13174            &mut bindings,
13175        ) {
13176            return false;
13177        }
13178    }
13179    true
13180}
13181
13182fn cpp_specialization_more_specialized(
13183    candidate: &CppTemplateMetadata,
13184    other: &CppTemplateMetadata,
13185) -> bool {
13186    cpp_specialization_pattern_accepts(other, candidate)
13187        && !cpp_specialization_pattern_accepts(candidate, other)
13188}
13189
13190fn cpp_specialization_pattern_accepts(
13191    broader: &CppTemplateMetadata,
13192    narrower: &CppTemplateMetadata,
13193) -> bool {
13194    if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
13195        return false;
13196    }
13197    let parameter_names = broader
13198        .parameters
13199        .iter()
13200        .map(|parameter| parameter.name.as_str())
13201        .collect::<HashSet<_>>();
13202    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
13203    broader
13204        .specialization_arguments
13205        .iter()
13206        .zip(&narrower.specialization_arguments)
13207        .all(|(pattern, argument)| {
13208            cpp_unify_template_term(
13209                &pattern.term,
13210                &argument.term,
13211                &parameter_names,
13212                &mut bindings,
13213            )
13214        })
13215}
13216
13217pub fn cpp_substitute_template_term(
13218    term: &CppTemplateTerm,
13219    bindings: &HashMap<String, CppTemplateTerm>,
13220) -> Option<CppTemplateTerm> {
13221    enum Work<'a> {
13222        Visit(&'a CppTemplateTerm),
13223        Build { kind: String, child_count: usize },
13224    }
13225
13226    let mut work = vec![Work::Visit(term)];
13227    let mut substituted = Vec::new();
13228    while let Some(next) = work.pop() {
13229        match next {
13230            Work::Visit(CppTemplateTerm::Parameter(name)) => {
13231                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
13232            }
13233            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
13234                substituted.push(CppTemplateTerm::Atom {
13235                    kind: kind.clone(),
13236                    text: text.clone(),
13237                });
13238            }
13239            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
13240                work.push(Work::Build {
13241                    kind: kind.clone(),
13242                    child_count: children.len(),
13243                });
13244                work.extend(children.iter().rev().map(Work::Visit));
13245            }
13246            Work::Build { kind, child_count } => {
13247                let children = substituted.split_off(substituted.len() - child_count);
13248                substituted.push(CppTemplateTerm::Node { kind, children });
13249            }
13250        }
13251    }
13252    substituted.pop()
13253}
13254
13255pub fn cpp_substitute_template_arguments(
13256    arguments: &[CppTemplateExpression],
13257    bindings: &HashMap<String, CppTemplateTerm>,
13258) -> Option<Vec<CppTemplateExpression>> {
13259    let mut substituted = Vec::new();
13260    for argument in arguments {
13261        let CppTemplateTerm::Node { kind, children } = &argument.term else {
13262            substituted.push(CppTemplateExpression {
13263                text: argument.text.clone(),
13264                term: cpp_substitute_template_term(&argument.term, bindings)?,
13265            });
13266            continue;
13267        };
13268        if kind != "parameter_pack_expansion" {
13269            substituted.push(CppTemplateExpression {
13270                text: argument.text.clone(),
13271                term: cpp_substitute_template_term(&argument.term, bindings)?,
13272            });
13273            continue;
13274        }
13275        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
13276            return None;
13277        };
13278        if ellipsis != "..." {
13279            return None;
13280        }
13281
13282        let mut pack_names = Vec::new();
13283        let mut work = vec![pattern];
13284        while let Some(term) = work.pop() {
13285            match term {
13286                CppTemplateTerm::Parameter(name)
13287                    if matches!(
13288                        bindings.get(name),
13289                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
13290                    ) =>
13291                {
13292                    if !pack_names.contains(name) {
13293                        pack_names.push(name.clone());
13294                    }
13295                }
13296                CppTemplateTerm::Node { children, .. } => work.extend(children),
13297                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
13298            }
13299        }
13300        let first_pack = pack_names.first()?;
13301        let CppTemplateTerm::Node {
13302            children: first_elements,
13303            ..
13304        } = bindings.get(first_pack)?
13305        else {
13306            return None;
13307        };
13308        let pack_len = first_elements.len();
13309        for pack_name in &pack_names {
13310            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
13311                return None;
13312            };
13313            if children.len() != pack_len {
13314                return None;
13315            }
13316        }
13317        for index in 0..pack_len {
13318            let mut element_bindings = bindings.clone();
13319            for pack_name in &pack_names {
13320                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
13321                    return None;
13322                };
13323                element_bindings.insert(
13324                    pack_name.clone(),
13325                    cpp_clone_template_term_iterative(&children[index]),
13326                );
13327            }
13328            substituted.push(CppTemplateExpression {
13329                text: argument.text.clone(),
13330                term: cpp_substitute_template_term(pattern, &element_bindings)?,
13331            });
13332        }
13333    }
13334    Some(substituted)
13335}
13336
13337fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
13338    enum Work<'a> {
13339        Visit(&'a CppTemplateTerm),
13340        Build { kind: String, child_count: usize },
13341    }
13342
13343    let mut work = vec![Work::Visit(term)];
13344    let mut cloned = Vec::new();
13345    while let Some(next) = work.pop() {
13346        match next {
13347            Work::Visit(CppTemplateTerm::Parameter(name)) => {
13348                cloned.push(CppTemplateTerm::Parameter(name.clone()));
13349            }
13350            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
13351                cloned.push(CppTemplateTerm::Atom {
13352                    kind: kind.clone(),
13353                    text: text.clone(),
13354                });
13355            }
13356            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
13357                work.push(Work::Build {
13358                    kind: kind.clone(),
13359                    child_count: children.len(),
13360                });
13361                work.extend(children.iter().rev().map(Work::Visit));
13362            }
13363            Work::Build { kind, child_count } => {
13364                let children = cloned.split_off(cloned.len() - child_count);
13365                cloned.push(CppTemplateTerm::Node { kind, children });
13366            }
13367        }
13368    }
13369    cloned
13370        .pop()
13371        .expect("template term traversal emits one root")
13372}
13373
13374fn cpp_clone_template_expression_iterative(
13375    expression: &CppTemplateExpression,
13376) -> CppTemplateExpression {
13377    CppTemplateExpression {
13378        text: expression.text.clone(),
13379        term: cpp_clone_template_term_iterative(&expression.term),
13380    }
13381}
13382
13383pub fn cpp_unify_template_term(
13384    pattern: &CppTemplateTerm,
13385    argument: &CppTemplateTerm,
13386    parameters: &HashSet<&str>,
13387    bindings: &mut HashMap<String, CppTemplateTerm>,
13388) -> bool {
13389    let mut work = vec![(pattern, argument)];
13390    while let Some((pattern, argument)) = work.pop() {
13391        match pattern {
13392            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
13393                if let Some(bound) = bindings.get(name) {
13394                    if !cpp_template_terms_equal(bound, argument) {
13395                        return false;
13396                    }
13397                } else {
13398                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
13399                }
13400            }
13401            CppTemplateTerm::Atom {
13402                kind: pattern_kind,
13403                text: pattern_text,
13404            } => {
13405                if !matches!(
13406                    argument,
13407                    CppTemplateTerm::Atom { kind, text }
13408                        if kind == pattern_kind && text == pattern_text
13409                ) {
13410                    return false;
13411                }
13412            }
13413            CppTemplateTerm::Node {
13414                kind: pattern_kind,
13415                children: pattern_children,
13416            } => {
13417                let CppTemplateTerm::Node { kind, children } = argument else {
13418                    return false;
13419                };
13420                if kind != pattern_kind || children.len() != pattern_children.len() {
13421                    return false;
13422                }
13423                work.extend(pattern_children.iter().zip(children).rev());
13424            }
13425            CppTemplateTerm::Parameter(_) => return false,
13426        }
13427    }
13428    true
13429}
13430
13431fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
13432    let mut work = vec![(left, right)];
13433    while let Some((left, right)) = work.pop() {
13434        match (left, right) {
13435            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
13436                if left != right {
13437                    return false;
13438                }
13439            }
13440            (
13441                CppTemplateTerm::Atom {
13442                    kind: left_kind,
13443                    text: left_text,
13444                },
13445                CppTemplateTerm::Atom {
13446                    kind: right_kind,
13447                    text: right_text,
13448                },
13449            ) => {
13450                if left_kind != right_kind || left_text != right_text {
13451                    return false;
13452                }
13453            }
13454            (
13455                CppTemplateTerm::Node {
13456                    kind: left_kind,
13457                    children: left_children,
13458                },
13459                CppTemplateTerm::Node {
13460                    kind: right_kind,
13461                    children: right_children,
13462                },
13463            ) => {
13464                if left_kind != right_kind || left_children.len() != right_children.len() {
13465                    return false;
13466                }
13467                work.extend(left_children.iter().zip(right_children).rev());
13468            }
13469            _ => return false,
13470        }
13471    }
13472    true
13473}
13474
13475pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
13476    let mut components = Vec::new();
13477    let mut stack = vec![node];
13478    while let Some(current) = stack.pop() {
13479        match current.kind() {
13480            "identifier"
13481            | "field_identifier"
13482            | "namespace_identifier"
13483            | "type_identifier"
13484            | "operator_name"
13485            | "destructor_name" => components.push(current),
13486            "template_type" | "template_function" => {
13487                stack.push(current.child_by_field_name("name")?);
13488            }
13489            "dependent_name" => stack.push(current.named_child(0)?),
13490            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13491                stack.push(current.child_by_field_name("name")?);
13492                if let Some(scope) = current.child_by_field_name("scope") {
13493                    stack.push(scope);
13494                }
13495            }
13496            "nested_namespace_specifier" => {
13497                for index in (0..current.named_child_count()).rev() {
13498                    stack.push(current.named_child(index)?);
13499                }
13500            }
13501            _ => return None,
13502        }
13503    }
13504    Some(components)
13505}
13506
13507pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
13508    node.child_by_field_name("scope").is_none()
13509        && node.child(0).is_some_and(|child| child.kind() == "::")
13510}
13511
13512fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
13513    let mut namespaces = Vec::new();
13514    let mut current = node.parent();
13515    while let Some(parent) = current {
13516        if parent.kind() == "namespace_definition"
13517            && let Some(name) = parent.child_by_field_name("name")
13518        {
13519            let mut components = Vec::new();
13520            append_cpp_name_components(name, source, &mut components)?;
13521            namespaces.push(components);
13522        }
13523        current = parent.parent();
13524    }
13525    namespaces.reverse();
13526    Some(namespaces.into_iter().flatten().collect())
13527}
13528
13529/// Whether a parser-derived namespace path can be reconciled with an indexed
13530/// owner scope without inventing an unrelated short-name binding.
13531///
13532/// Macro namespace sentinels can make tree-sitter omit one or more namespace
13533/// definitions from the ancestor chain. Preserve the order of every namespace
13534/// that did survive parsing, but allow indexed components between them. An
13535/// empty path is accepted only when the declarator itself supplies a nested
13536/// owner suffix such as `Outer::Inner`: together with the indexed enclosing
13537/// owner chain, that suffix is structural evidence that a namespace was lost.
13538/// A one-segment owner at the translation-unit root remains insufficient.
13539fn indexed_namespace_path_is_recoverable(
13540    lexical_scope: &[String],
13541    indexed_owner_scope: &[String],
13542    explicit_owner_component_count: usize,
13543) -> bool {
13544    if lexical_scope.is_empty() {
13545        return explicit_owner_component_count > 1;
13546    }
13547    if lexical_scope.len() >= indexed_owner_scope.len() {
13548        return false;
13549    }
13550    let mut indexed = indexed_owner_scope.iter();
13551    lexical_scope
13552        .iter()
13553        .all(|component| indexed.any(|candidate| candidate == component))
13554}
13555
13556pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
13557    let mut current = node.parent();
13558    while let Some(parent) = current {
13559        if parent.kind() == kind {
13560            return true;
13561        }
13562        current = parent.parent();
13563    }
13564    false
13565}
13566
13567/// Whether a declaration type is initialized with a pointer cast.
13568///
13569/// This structured shape has an independent qualified occurrence in addition
13570/// to the cast descriptor below it. Other declarations must keep their normal
13571/// full-range occurrence only.
13572pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
13573    let mut current = Some(node);
13574    while let Some(candidate) = current {
13575        if candidate.kind() == "declaration" {
13576            let Some(type_node) = candidate.child_by_field_name("type") else {
13577                return false;
13578            };
13579            if !(type_node.start_byte() <= node.start_byte()
13580                && node.end_byte() <= type_node.end_byte())
13581            {
13582                return false;
13583            }
13584            let mut cursor = candidate.walk();
13585            return candidate.named_children(&mut cursor).any(|child| {
13586                child.kind() == "init_declarator"
13587                    && child
13588                        .child_by_field_name("value")
13589                        .is_some_and(|value| value.kind() == "cast_expression")
13590            });
13591        }
13592        current = candidate.parent();
13593    }
13594    false
13595}
13596
13597#[derive(Clone, Copy, PartialEq, Eq)]
13598pub(crate) enum QualifiedAliasReferenceKind {
13599    Ordinary,
13600    ConstructorWithExpressionArgument,
13601    ExhaustiveTemplate,
13602}
13603
13604/// Whether a qualified alias reference preserves the requested target.
13605///
13606/// The complete qualified spelling and its terminal identifier are both valid
13607/// occurrences when the visible alias path is structurally proven to name the
13608/// target. Template aliases use their bound arguments; ordinary aliases use
13609/// their structured primary chain.
13610pub(crate) fn qualified_alias_reference_preserves_target(
13611    node: Node<'_>,
13612    target: &CodeUnit,
13613    analyzer: &CppGraphSource<'_>,
13614    visibility: &VisibilityIndex<'_>,
13615    file: &ProjectFile,
13616    source: &str,
13617) -> Option<QualifiedAliasReferenceKind> {
13618    if !matches!(
13619        node.kind(),
13620        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
13621    ) {
13622        return None;
13623    }
13624    let components = cpp_type_name_components(node, source)?;
13625    let name = components.last()?;
13626    analyzer.type_alias_provider().and_then(|provider| {
13627        visibility
13628            .visible_identifier_candidates(file, name)
13629            .find_map(|candidate| {
13630                let proof = provider.is_type_alias(candidate)
13631                    && canonical_cpp_scope_components(candidate) == components
13632                    && visibility.external_type_candidate_visible_in_context(
13633                        analyzer, file, candidate, node,
13634                    )
13635                    && match cpp_template_reference_arguments(node, source) {
13636                        Some(arguments) => visibility.template_alias_arguments_preserve_target(
13637                            analyzer, file, candidate, &arguments, target,
13638                        ),
13639                        None => visibility.structured_alias_primary_preserves_target(
13640                            analyzer, file, candidate, target,
13641                        ),
13642                    };
13643                proof.then(|| {
13644                    if cpp_template_reference_arguments(node, source).is_some()
13645                        && visibility.is_exhaustive_same_fqn_type_declaration_family(
13646                            analyzer, file, candidate,
13647                        )
13648                    {
13649                        QualifiedAliasReferenceKind::ExhaustiveTemplate
13650                    } else if qualified_alias_constructor_has_expression_argument(node)
13651                        || qualified_alias_local_constructor_declaration(node)
13652                    {
13653                        QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
13654                    } else {
13655                        QualifiedAliasReferenceKind::Ordinary
13656                    }
13657                })
13658            })
13659    })
13660}
13661
13662pub(crate) fn qualified_alias_reference_requires_terminal(
13663    reference: Option<QualifiedAliasReferenceKind>,
13664) -> bool {
13665    matches!(
13666        reference,
13667        Some(
13668            QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
13669                | QualifiedAliasReferenceKind::ExhaustiveTemplate
13670        )
13671    )
13672}
13673
13674fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
13675    let Some(declaration) = node.parent().filter(|parent| {
13676        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
13677    }) else {
13678        return false;
13679    };
13680    let mut cursor = declaration.walk();
13681    declaration.named_children(&mut cursor).any(|child| {
13682        child.kind() == "init_declarator"
13683            && child
13684                .child_by_field_name("value")
13685                .filter(|value| value.kind() == "argument_list")
13686                .is_some_and(|arguments| {
13687                    let mut cursor = arguments.walk();
13688                    arguments.named_children(&mut cursor).any(|argument| {
13689                        let is_parameter = matches!(
13690                            argument.kind(),
13691                            "parameter_declaration" | "optional_parameter_declaration"
13692                        );
13693                        if is_parameter {
13694                            argument
13695                                .child_by_field_name("type")
13696                                .is_some_and(|type_node| {
13697                                    type_node.kind() == "type_identifier"
13698                                        && argument.child_by_field_name("declarator").is_none()
13699                                })
13700                        } else {
13701                            !argument.kind().ends_with("_literal")
13702                                && !matches!(argument.kind(), "true" | "false" | "nullptr")
13703                        }
13704                    })
13705                })
13706    })
13707}
13708
13709/// Tree-sitter represents a local C++ direct construction such as
13710/// `Alias value(argument)` as a function declarator. Restrict that recovery to
13711/// declarations inside a compound statement so namespace-scope function
13712/// declarations with the same qualified return type stay full-range only.
13713fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
13714    let Some(declaration) = node.parent().filter(|parent| {
13715        parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
13716    }) else {
13717        return false;
13718    };
13719    if declaration
13720        .parent()
13721        .is_none_or(|parent| parent.kind() != "compound_statement")
13722    {
13723        return false;
13724    }
13725    let mut cursor = declaration.walk();
13726    declaration
13727        .named_children(&mut cursor)
13728        .any(|child| child.kind() == "function_declarator")
13729}
13730
13731/// Return the terminal identifier represented by a callable or type callee.
13732///
13733/// Qualified, scoped, template, and field wrappers are traversed through their
13734/// grammar fields so both function calls and type constructions emit the token
13735/// that names the referenced declaration.
13736pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
13737    loop {
13738        let next = match node.kind() {
13739            "qualified_identifier"
13740            | "scoped_identifier"
13741            | "template_method"
13742            | "template_function"
13743            | "template_type" => node.child_by_field_name("name"),
13744            "field_expression" => node.child_by_field_name("field"),
13745            _ => None,
13746        };
13747        let Some(next) = next else {
13748            return node;
13749        };
13750        node = next;
13751    }
13752}
13753
13754#[derive(Clone, Copy)]
13755pub struct RecoveredRelationalTemplateMemberCall<'tree> {
13756    pub receiver: Node<'tree>,
13757    pub member: Node<'tree>,
13758    pub arity: usize,
13759}
13760
13761/// Recover `receiver.member<argument>(call_arguments)` when tree-sitter chose
13762/// nested relational expressions instead of a `template_method` call.
13763///
13764/// The recovery uses only grammar fields: the selected field must be the left
13765/// side of `<`, that expression must be the left side of `>`, and the right
13766/// side of `>` must be the parenthesized call arguments. Semantic callers must
13767/// additionally prove the receiver owner and the member's template status.
13768pub fn recovered_relational_template_member_call(
13769    field: Node<'_>,
13770) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
13771    if field.kind() != "field_expression" {
13772        return None;
13773    }
13774    let receiver = field
13775        .child_by_field_name("argument")
13776        .or_else(|| field.child_by_field_name("object"))?;
13777    let member = field.child_by_field_name("field")?;
13778    let less = field.parent()?;
13779    if less.kind() != "binary_expression"
13780        || less.child_by_field_name("left") != Some(field)
13781        || less
13782            .child_by_field_name("operator")
13783            .is_none_or(|operator| operator.kind() != "<")
13784        || less.child_by_field_name("right").is_none()
13785    {
13786        return None;
13787    }
13788    let greater = less.parent()?;
13789    if greater.kind() != "binary_expression"
13790        || greater.child_by_field_name("left") != Some(less)
13791        || greater
13792            .child_by_field_name("operator")
13793            .is_none_or(|operator| operator.kind() != ">")
13794    {
13795        return None;
13796    }
13797    let arguments = greater.child_by_field_name("right")?;
13798    if arguments.kind() != "parenthesized_expression" {
13799        return None;
13800    }
13801    let arity = parenthesized_call_argument_arity(arguments)?;
13802    Some(RecoveredRelationalTemplateMemberCall {
13803        receiver,
13804        member,
13805        arity,
13806    })
13807}
13808
13809fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
13810    let expression = arguments.named_child(0)?;
13811    if expression.kind() != "comma_expression" {
13812        return Some(1);
13813    }
13814    let mut arity = 0usize;
13815    let mut stack = vec![expression];
13816    while let Some(node) = stack.pop() {
13817        if node.kind() == "comma_expression" {
13818            stack.push(node.child_by_field_name("right")?);
13819            stack.push(node.child_by_field_name("left")?);
13820        } else {
13821            arity += 1;
13822        }
13823    }
13824    Some(arity)
13825}
13826
13827/// Whether `node` is part of a call's callee expression, walking only through
13828/// the grammar wrappers that can structurally contain that callee.
13829pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
13830    while let Some(parent) = node.parent() {
13831        match parent.kind() {
13832            "call_expression" => {
13833                return parent
13834                    .child_by_field_name("function")
13835                    .or_else(|| parent.named_child(0))
13836                    == Some(node);
13837            }
13838            "qualified_identifier"
13839            | "scoped_identifier"
13840            | "template_function"
13841            | "template_type"
13842            | "field_expression" => node = parent,
13843            _ => return false,
13844        }
13845    }
13846    false
13847}
13848
13849pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
13850    if is_call_callee_node(node) {
13851        function_terminal_node(node)
13852    } else {
13853        node
13854    }
13855}
13856
13857pub fn normalize_type_text(value: &str) -> String {
13858    strip_tag_type_prefix(
13859        normalize_cpp_whitespace(value)
13860            .trim_start_matches("const ")
13861            .trim_end_matches('*')
13862            .trim_end_matches('&')
13863            .trim(),
13864    )
13865    .to_string()
13866}
13867
13868fn strip_tag_type_prefix(value: &str) -> &str {
13869    let value = value.trim_start_matches("const ");
13870    value
13871        .strip_prefix("struct ")
13872        .or_else(|| value.strip_prefix("class "))
13873        .or_else(|| value.strip_prefix("enum "))
13874        .unwrap_or(value)
13875        .trim()
13876}
13877
13878pub fn normalize_reference_name(value: &str) -> Option<String> {
13879    let normalized = normalize_cpp_reference_text(value);
13880    (!normalized.is_empty()).then_some(normalized)
13881}
13882
13883pub fn normalize_cpp_reference_text(value: &str) -> String {
13884    let mut text = normalize_cpp_whitespace(value)
13885        .trim_start_matches("new ")
13886        .trim()
13887        .to_string();
13888    if let Some(index) = text.find(['(', '{']) {
13889        text.truncate(index);
13890    }
13891    if let Some(index) = text.find('<') {
13892        text.truncate(index);
13893    }
13894    let normalized = text
13895        .trim()
13896        .trim_start_matches("const ")
13897        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
13898        .trim_matches(':')
13899        .trim();
13900    strip_tag_type_prefix(normalized).to_string()
13901}
13902
13903pub fn cpp_name_for(unit: &CodeUnit) -> String {
13904    let short = unit.short_name().replace(['.', '$'], "::");
13905    if unit.package_name().is_empty() {
13906        short
13907    } else {
13908        format!("{}::{}", unit.package_name(), short)
13909    }
13910}
13911
13912/// Render an indexed C++ qualified name from its authoritative FqName
13913/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
13914/// that belong to a template argument (for example `Args...`).
13915fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
13916    let fq = unit.fq();
13917    if fq.is_empty() {
13918        return None;
13919    }
13920    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
13921    Some(
13922        fq.segments()
13923            .iter()
13924            .map(|&segment| interner.resolve(segment).0)
13925            .collect::<Vec<_>>()
13926            .join("::"),
13927    )
13928}
13929
13930fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
13931    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
13932        || unit.fq().is_empty() && cpp_name_for(unit) == expected
13933}
13934
13935/// Return the indexed C++ owner scope without reparsing its rendered name.
13936///
13937/// Template spellings are opaque within an indexed `FqName` segment.  In
13938/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
13939/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
13940/// through `parse_symbol_path` would mistake those dots for component
13941/// separators.  Cache-loaded/legacy units may still have an empty structured
13942/// name, so retain the parser only as that explicit fallback.
13943pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
13944    let fq = unit.fq();
13945    if !fq.is_empty() {
13946        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
13947        let scope = fq
13948            .segments()
13949            .iter()
13950            .filter_map(|&segment| {
13951                let (text, kind) = interner.resolve(segment);
13952                matches!(
13953                    kind,
13954                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
13955                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
13956                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
13957                )
13958                .then(|| text.to_string())
13959            })
13960            .collect();
13961        return scope;
13962    }
13963    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13964        brokk_bifrost_core::analyzer::Language::Cpp,
13965        &cpp_name_for(unit),
13966    )
13967}
13968
13969// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
13970// (not the substring "->"), which deliberately reduces an `operator->`-style
13971// terminal segment to an empty tail rather than keeping it intact; the shared
13972// structured splitter's cpp operator-token merge would keep `operator->`
13973// whole instead, changing this function's result — `name_matches_callable`'s
13974// `expected.starts_with("operator")` fallback exists specifically to
13975// compensate for that reduction, and a pinned regression test
13976// (`operator-> must not be reduced with terminal_name-style punctuation
13977// splitting`) asserts today's char-class behavior. Not equivalence-provable;
13978// revisit alongside that pinned test if it is ever relaxed.
13979pub fn terminal_name(value: &str) -> &str {
13980    value
13981        .rsplit("::")
13982        .next()
13983        .unwrap_or(value)
13984        .rsplit(['.', '-', '>'])
13985        .next()
13986        .unwrap_or(value)
13987        .trim()
13988}
13989
13990pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
13991    terminal_name(&normalize_cpp_reference_text(value)) == expected
13992}
13993
13994pub fn name_matches_callable(value: &str, expected: &str) -> bool {
13995    name_matches_terminal(value, expected)
13996        || expected.starts_with("operator")
13997            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
13998}
13999
14000pub fn name_mentions(value: &str, expected: &str) -> bool {
14001    normalize_cpp_reference_text(value)
14002        .split("::")
14003        .any(|part| part == expected)
14004}
14005
14006pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
14007    let cpp_name = cpp_name_for(unit);
14008    if reference.contains("::") {
14009        return reference == cpp_name;
14010    }
14011    reference == cpp_name
14012        || terminal_name(reference) == unit.identifier()
14013            && (unit.package_name().is_empty() || reference == unit.identifier())
14014}
14015
14016pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
14017    match kind {
14018        TargetKind::Type
14019        | TargetKind::Constructor
14020        | TargetKind::Method
14021        | TargetKind::MemberField => true,
14022        TargetKind::FreeFunction => unit.is_function(),
14023        TargetKind::GlobalField => unit.is_field(),
14024        TargetKind::Macro => unit.is_macro(),
14025    }
14026}
14027
14028pub fn is_type_alias(unit: &CodeUnit) -> bool {
14029    unit.kind() == CodeUnitType::Field
14030        && unit.signature().is_some_and(|signature| {
14031            signature.starts_with("typedef ") || signature.starts_with("using ")
14032        })
14033}
14034
14035fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
14036    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
14037    let target_name = cpp_name_for(target);
14038    if normalized.contains("::") {
14039        return normalized == target_name;
14040    }
14041    if let Some(namespace) = alias.namespace.as_deref() {
14042        return namespace_prefixes(namespace)
14043            .into_iter()
14044            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
14045    }
14046    target.package_name().is_empty() && normalized == target.identifier()
14047}
14048
14049/// The declared return type text of a C++ function unit, with leading declaration specifiers
14050/// stripped, e.g. `T*` for `T* operator->()`.
14051pub fn cpp_function_return_type_text(
14052    analyzer: &CppGraphSource<'_>,
14053    function: &CodeUnit,
14054) -> Option<String> {
14055    let metadata = analyzer.signature_metadata(function);
14056    if !metadata.is_empty() {
14057        let first = metadata.first()?.return_type_text()?;
14058        return metadata
14059            .iter()
14060            .all(|metadata| metadata.return_type_text() == Some(first))
14061            .then(|| first.to_string());
14062    }
14063    let signature = cpp_function_signature_text(analyzer, function)?;
14064    cpp_function_return_type_text_from_signature(&signature)
14065}
14066
14067fn cpp_function_signature_text(
14068    analyzer: &CppGraphSource<'_>,
14069    function: &CodeUnit,
14070) -> Option<String> {
14071    function
14072        .signature()
14073        .filter(|signature| signature.contains(function.identifier()))
14074        .map(str::to_string)
14075        .or_else(|| analyzer.signatures(function).first().cloned())
14076        .or_else(|| analyzer.get_source(function, false))
14077}
14078
14079fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
14080    let open = signature.find('(')?;
14081    let name_at = cpp_function_name_start(signature, open)?;
14082    if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
14083        return Some(return_type);
14084    }
14085    let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
14086        .split_whitespace()
14087        .filter(|token| {
14088            !matches!(
14089                *token,
14090                "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
14091            )
14092        })
14093        .collect::<Vec<_>>()
14094        .join(" ");
14095    let type_text = type_text.trim();
14096    (!type_text.is_empty()).then(|| type_text.to_string())
14097}
14098
14099fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
14100    let before_parameters = &signature[..open];
14101    if let Some(operator_at) = before_parameters.rfind("operator") {
14102        let boundary = operator_at == 0
14103            || before_parameters[..operator_at]
14104                .chars()
14105                .next_back()
14106                .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
14107        if boundary {
14108            return Some(operator_at);
14109        }
14110    }
14111    before_parameters
14112        .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
14113        .map(|index| index + 1)
14114}
14115
14116fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
14117    let open = signature_from_name.find('(')?;
14118    let mut depth = 0i32;
14119    for (offset, ch) in signature_from_name[open..].char_indices() {
14120        match ch {
14121            '(' => depth += 1,
14122            ')' => {
14123                depth -= 1;
14124                if depth == 0 {
14125                    let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
14126                    let arrow = rest.find("->")?;
14127                    let return_type = rest[arrow + 2..].trim_start();
14128                    let return_type = return_type
14129                        .split(['{', ';'])
14130                        .next()
14131                        .unwrap_or(return_type)
14132                        .trim();
14133                    return (!return_type.is_empty()).then(|| return_type.to_string());
14134                }
14135            }
14136            _ => {}
14137        }
14138    }
14139    None
14140}
14141
14142/// Strip a leading `template <...>` parameter clause, leaving the declaration that follows.
14143/// Returns the input unchanged when there is no such clause.
14144fn cpp_strip_leading_template_clause(text: &str) -> &str {
14145    let trimmed = text.trim_start();
14146    let Some(rest) = trimmed.strip_prefix("template") else {
14147        return text;
14148    };
14149    let rest = rest.trim_start();
14150    if !rest.starts_with('<') {
14151        return text;
14152    }
14153    let mut depth = 0i32;
14154    for (offset, ch) in rest.char_indices() {
14155        match ch {
14156            '<' => depth += 1,
14157            '>' => {
14158                depth -= 1;
14159                if depth == 0 {
14160                    return rest[offset + ch.len_utf8()..].trim_start();
14161                }
14162            }
14163            _ => {}
14164        }
14165    }
14166    text
14167}
14168
14169pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
14170    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
14171    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
14172    // the same string `default_parent_fq_name`/`fq().parent()` would render:
14173    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
14174    // `::`) between a trailing `Package` segment and a following `Type`
14175    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
14176    // popping the unit's own `fq()` segment would NOT reproduce this
14177    // fully-`::`-joined string. Left as a split on the locally-built
14178    // all-colon string rather than the unit's structured name.
14179    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
14180        namespace
14181            .strip_prefix("anonymous_namespace::")
14182            .unwrap_or(namespace)
14183            .to_string()
14184    })
14185}
14186
14187fn namespace_prefixes(namespace: &str) -> Vec<String> {
14188    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
14189    // non-`::` separator already converted to `::`, so re-tokenizing it with
14190    // the shared structured splitter and progressively popping the last
14191    // component reproduces the `rsplit_once("::")` outward walk exactly (same
14192    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
14193    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
14194        brokk_bifrost_core::analyzer::Language::Cpp,
14195        namespace,
14196    );
14197    let mut prefixes = Vec::new();
14198    while !parts.is_empty() {
14199        prefixes.push(parts.join("::"));
14200        parts.pop();
14201    }
14202    prefixes
14203}
14204
14205fn nearest_namespace_candidates(
14206    candidates: Vec<CodeUnit>,
14207    normalized: &str,
14208    lexical_namespace: Option<&str>,
14209) -> Vec<CodeUnit> {
14210    if normalized.contains("::") {
14211        return candidates;
14212    }
14213    if let Some(namespace) = lexical_namespace {
14214        for prefix in namespace_prefixes(namespace) {
14215            let scoped = candidates
14216                .iter()
14217                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
14218                .cloned()
14219                .collect::<Vec<_>>();
14220            if !scoped.is_empty() {
14221                return scoped;
14222            }
14223        }
14224    }
14225    candidates
14226        .into_iter()
14227        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
14228        .collect()
14229}
14230
14231pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
14232    let mut namespaces = Vec::new();
14233    let mut current = node.parent();
14234    while let Some(parent) = current {
14235        if parent.kind() == "namespace_definition"
14236            && let Some(name) = parent.child_by_field_name("name")
14237        {
14238            let namespace = normalize_cpp_reference_text(node_text(name, source));
14239            if !namespace.is_empty() {
14240                namespaces.push(namespace);
14241            }
14242        }
14243        current = parent.parent();
14244    }
14245    if namespaces.is_empty() {
14246        None
14247    } else {
14248        namespaces.reverse();
14249        Some(namespaces.join("::"))
14250    }
14251}
14252
14253/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
14254/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
14255/// globals rather than members.
14256pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
14257    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
14258}
14259
14260fn type_owner_resolution(
14261    analyzer: &CppGraphSource<'_>,
14262    code_unit: &CodeUnit,
14263) -> Option<ResolvedTypeOwner> {
14264    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
14265}
14266
14267fn target_type_owner_resolution(
14268    analyzer: &CppGraphSource<'_>,
14269    code_unit: &CodeUnit,
14270) -> Option<ResolvedTypeOwner> {
14271    match type_owner_resolution(analyzer, code_unit) {
14272        Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
14273        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
14274    }
14275}
14276
14277/// Recover method identity for an indexed out-of-line definition when the
14278/// ordinary parent edge is absent. Prefer the unique include-visible forward
14279/// declaration, then classify exact-FQN class declarations elsewhere in the
14280/// workspace. A unique complete declaration wins; otherwise multiple forward
14281/// declarations are one owner only when they all share one logical identity.
14282/// The qualified callable FQN proves that owner spelling even when its defining
14283/// header is outside the scan file's include closure, while unknown or competing
14284/// complete declarations remain ambiguous.
14285/// This is deliberately target-only: canonical declaration resolution must
14286/// continue to prefer the callable definition rather than replacing it with
14287/// the recovered owner.
14288fn target_forward_owner_resolution(
14289    analyzer: &CppGraphSource<'_>,
14290    code_unit: &CodeUnit,
14291) -> Option<ResolvedTypeOwner> {
14292    if !code_unit.is_function() {
14293        return None;
14294    }
14295    // A top-level free function has no owner at all, and `FqName::parent`
14296    // answers the empty name rather than `None` for a one-segment identity.
14297    // `default_parent_fq_name`, which this replaced, filtered that case out;
14298    // asking the relational store for the empty name is a batch error that
14299    // fails the whole target frontier.
14300    let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
14301    let cpp = analyzer.cpp?;
14302    let mut visible_files = HashSet::default();
14303    collect_include_closure(
14304        analyzer,
14305        cpp.include_target_index(),
14306        code_unit.source(),
14307        &mut visible_files,
14308        None,
14309    );
14310    let candidates = analyzer.workspace_definitions().exact(&owner_name);
14311    let visible_candidates = candidates
14312        .iter()
14313        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
14314        .cloned()
14315        .collect::<Vec<_>>();
14316    match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
14317        DirectOwnerResolution::UniqueFull(unit) => {
14318            return Some(ResolvedTypeOwner {
14319                unit,
14320                is_forward_declaration: false,
14321            });
14322        }
14323        DirectOwnerResolution::ForwardsOnly(forwards) => {
14324            return (forwards.len() == 1).then(|| ResolvedTypeOwner {
14325                unit: forwards.into_iter().next().unwrap(),
14326                is_forward_declaration: true,
14327            });
14328        }
14329        DirectOwnerResolution::Ambiguous => return None,
14330        DirectOwnerResolution::None => {}
14331    }
14332
14333    let candidates = candidates
14334        .into_iter()
14335        .filter(|candidate| candidate.is_class())
14336        .collect::<Vec<_>>();
14337    let (unit, is_forward_declaration) =
14338        match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
14339            DirectOwnerResolution::UniqueFull(unit) => (unit, false),
14340            DirectOwnerResolution::ForwardsOnly(forwards) => {
14341                (unique_logical_forward_owner(forwards)?, true)
14342            }
14343            DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
14344        };
14345    Some(ResolvedTypeOwner {
14346        unit,
14347        is_forward_declaration,
14348    })
14349}
14350
14351pub fn precise_parent_of(
14352    analyzer: &CppGraphSource<'_>,
14353    visibility: &VisibilityIndex<'_>,
14354    code_unit: &CodeUnit,
14355) -> Option<CodeUnit> {
14356    visibility.cached_precise_parent_of(analyzer, code_unit)
14357}
14358
14359fn precise_parent_resolution(
14360    analyzer: &CppGraphSource<'_>,
14361    code_unit: &CodeUnit,
14362) -> Option<ResolvedTypeOwner> {
14363    #[cfg(any(test, feature = "test-support"))]
14364    if let Some(cpp) = analyzer.cpp {
14365        cpp.record_cpp_parent_resolution_for_test();
14366    }
14367    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
14368        return Some(ResolvedTypeOwner {
14369            unit,
14370            is_forward_declaration: false,
14371        });
14372    }
14373    let fallback = analyzer.parent_of(code_unit);
14374    if !code_unit.owner_is_type_scope() {
14375        return fallback.map(|unit| ResolvedTypeOwner {
14376            unit,
14377            is_forward_declaration: false,
14378        });
14379    }
14380    let owner_fq = code_unit
14381        .fq()
14382        .parent()
14383        .expect("a unit with an owner identifier has a structured parent");
14384    let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
14385    match same_source_owner(analyzer, code_unit, &owner_candidates) {
14386        DirectOwnerResolution::UniqueFull(owner) => {
14387            return Some(ResolvedTypeOwner {
14388                unit: owner,
14389                is_forward_declaration: false,
14390            });
14391        }
14392        DirectOwnerResolution::Ambiguous => return None,
14393        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
14394    }
14395    match directly_included_owner(analyzer, code_unit, &owner_candidates) {
14396        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
14397            unit: owner,
14398            is_forward_declaration: false,
14399        }),
14400        DirectOwnerResolution::Ambiguous => None,
14401        DirectOwnerResolution::ForwardsOnly(forwards) => {
14402            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
14403                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
14404                    unit: owner,
14405                    is_forward_declaration: false,
14406                }),
14407                FullOwnerResolution::None => {
14408                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
14409                        unit,
14410                        is_forward_declaration: true,
14411                    })
14412                }
14413                FullOwnerResolution::Ambiguous => None,
14414            }
14415        }
14416        DirectOwnerResolution::None => {
14417            match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
14418                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
14419                    unit: owner,
14420                    is_forward_declaration: false,
14421                }),
14422                FullOwnerResolution::Ambiguous => None,
14423                FullOwnerResolution::None => fallback
14424                    .filter(|parent| {
14425                        parent.source() == code_unit.source()
14426                            && parent.fq() == &owner_fq
14427                            && (!parent.is_class()
14428                                || cpp_class_declaration_strength(analyzer, parent)
14429                                    == CppClassDeclarationStrength::Full)
14430                    })
14431                    .map(|unit| ResolvedTypeOwner {
14432                        unit,
14433                        is_forward_declaration: false,
14434                    }),
14435            }
14436        }
14437    }
14438}
14439
14440fn exact_structural_type_parent(
14441    analyzer: &CppGraphSource<'_>,
14442    code_unit: &CodeUnit,
14443) -> Option<CodeUnit> {
14444    if !code_unit.is_function() && !code_unit.is_field() {
14445        return None;
14446    }
14447    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
14448    let cpp = analyzer.cpp?;
14449    let parent = cpp.structural_parent_of(code_unit)?;
14450    (!parent.is_module()
14451        && parent.source() == code_unit.source()
14452        && parent.package_name() == code_unit.package_name()
14453        && parent.short_name() == encoded_owner)
14454        .then_some(parent)
14455}
14456
14457fn same_source_owner(
14458    analyzer: &CppGraphSource<'_>,
14459    code_unit: &CodeUnit,
14460    owner_candidates: &[CodeUnit],
14461) -> DirectOwnerResolution {
14462    let candidates = owner_candidates
14463        .iter()
14464        .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
14465        .cloned()
14466        .collect::<Vec<_>>();
14467    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
14468    classify_direct_owner_candidates(analyzer, candidates.into_iter())
14469}
14470
14471fn visible_full_cpp_owner(
14472    analyzer: &CppGraphSource<'_>,
14473    code_unit: &CodeUnit,
14474    owner_candidates: &[CodeUnit],
14475) -> FullOwnerResolution {
14476    let Some(cpp) = analyzer.cpp else {
14477        return FullOwnerResolution::None;
14478    };
14479    let mut visible_files = HashSet::default();
14480    collect_include_closure(
14481        analyzer,
14482        cpp.include_target_index(),
14483        code_unit.source(),
14484        &mut visible_files,
14485        None,
14486    );
14487    let candidates = owner_candidates
14488        .iter()
14489        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
14490        .cloned()
14491        .collect::<Vec<_>>();
14492    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
14493    let mut full_definition = None;
14494    for candidate in candidates {
14495        match cpp_class_declaration_strength(analyzer, &candidate) {
14496            CppClassDeclarationStrength::Full if full_definition.is_some() => {
14497                return FullOwnerResolution::Ambiguous;
14498            }
14499            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
14500            CppClassDeclarationStrength::Forward => {}
14501            CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
14502        }
14503    }
14504    full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
14505}
14506
14507pub enum DirectOwnerResolution {
14508    None,
14509    ForwardsOnly(Vec<CodeUnit>),
14510    UniqueFull(CodeUnit),
14511    Ambiguous,
14512}
14513
14514enum FullOwnerResolution {
14515    None,
14516    Unique(CodeUnit),
14517    Ambiguous,
14518}
14519
14520#[derive(Clone, Copy, PartialEq, Eq)]
14521pub enum CppClassDeclarationStrength {
14522    Full,
14523    Forward,
14524    Unknown,
14525}
14526
14527fn directly_included_owner(
14528    analyzer: &CppGraphSource<'_>,
14529    code_unit: &CodeUnit,
14530    owner_candidates: &[CodeUnit],
14531) -> DirectOwnerResolution {
14532    let Some(cpp) = analyzer.cpp else {
14533        return DirectOwnerResolution::None;
14534    };
14535    let imports = analyzer.import_statements(code_unit.source());
14536    let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
14537        .into_iter()
14538        .flat_map(|include| {
14539            resolve_include_targets_with_index(
14540                code_unit.source(),
14541                &include,
14542                cpp.include_target_index(),
14543            )
14544        })
14545        .collect();
14546    let candidates = owner_candidates
14547        .iter()
14548        .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
14549        .cloned()
14550        .collect::<Vec<_>>();
14551    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
14552    classify_direct_owner_candidates(analyzer, candidates.into_iter())
14553}
14554
14555fn prefer_member_declaring_owners(
14556    analyzer: &CppGraphSource<'_>,
14557    member: &CodeUnit,
14558    candidates: Vec<CodeUnit>,
14559) -> Vec<CodeUnit> {
14560    let matching = candidates
14561        .iter()
14562        .filter(|owner| owner_declares_member(analyzer, owner, member))
14563        .cloned()
14564        .collect::<Vec<_>>();
14565    if matching.is_empty() {
14566        candidates
14567    } else {
14568        matching
14569    }
14570}
14571
14572fn owner_declares_member(
14573    analyzer: &CppGraphSource<'_>,
14574    owner: &CodeUnit,
14575    member: &CodeUnit,
14576) -> bool {
14577    analyzer.direct_children(owner).into_iter().any(|child| {
14578        child.kind() == member.kind()
14579            && child.identifier() == member.identifier()
14580            && child.signature() == member.signature()
14581    })
14582}
14583
14584fn classify_direct_owner_candidates(
14585    analyzer: &CppGraphSource<'_>,
14586    candidates: impl Iterator<Item = CodeUnit>,
14587) -> DirectOwnerResolution {
14588    collapse_owner_candidates(candidates.map(|candidate| {
14589        let strength = cpp_class_declaration_strength(analyzer, &candidate);
14590        (candidate, strength)
14591    }))
14592}
14593
14594pub fn collapse_owner_candidates(
14595    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
14596) -> DirectOwnerResolution {
14597    let mut full_definition = None;
14598    let mut forwards = Vec::new();
14599    for (candidate, strength) in candidates {
14600        match strength {
14601            CppClassDeclarationStrength::Full if full_definition.is_some() => {
14602                return DirectOwnerResolution::Ambiguous;
14603            }
14604            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
14605            CppClassDeclarationStrength::Forward => forwards.push(candidate),
14606            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
14607        }
14608    }
14609    if let Some(owner) = full_definition {
14610        DirectOwnerResolution::UniqueFull(owner)
14611    } else if !forwards.is_empty() {
14612        DirectOwnerResolution::ForwardsOnly(forwards)
14613    } else {
14614        DirectOwnerResolution::None
14615    }
14616}
14617
14618#[cfg(any(test, feature = "test-support"))]
14619pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
14620    unique_logical_forward_owner(forwards)
14621}
14622
14623fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
14624    let first = forwards.pop()?;
14625    forwards
14626        .iter()
14627        .all(|forward| same_logical_symbol(forward, &first))
14628        .then_some(first)
14629}
14630
14631pub fn cpp_class_declaration_strength(
14632    analyzer: &CppGraphSource<'_>,
14633    candidate: &CodeUnit,
14634) -> CppClassDeclarationStrength {
14635    // The answer is a pure function of the unit's ranges and its file's tree,
14636    // and the inverse scan asks it once per declaration seed. On a translation
14637    // unit the parser could not fully recover, each ask re-derives the
14638    // export-macro recovery shapes from the file's `ERROR` subtrees, so without
14639    // this memo one file's scan is quadratic in its own size: 97% of Catch2's
14640    // 284 s inverse scan of `extras/catch_amalgamated.cpp` was in this call
14641    // (#1496).
14642    let Some(cpp) = analyzer.cpp else {
14643        return uncached_cpp_class_declaration_strength(analyzer, candidate);
14644    };
14645    if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
14646        return strength;
14647    }
14648    let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
14649    cpp.cache_class_declaration_strength(candidate, strength);
14650    strength
14651}
14652
14653fn uncached_cpp_class_declaration_strength(
14654    analyzer: &CppGraphSource<'_>,
14655    candidate: &CodeUnit,
14656) -> CppClassDeclarationStrength {
14657    if let Some(cpp) = analyzer.cpp
14658        && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
14659    {
14660        return cpp_class_declaration_strength_in_tree(
14661            analyzer,
14662            &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
14663            candidate,
14664            prepared.source(),
14665            prepared.tree().root_node(),
14666        );
14667    }
14668    let Some(source) = analyzer.indexed_source(candidate.source()) else {
14669        return CppClassDeclarationStrength::Unknown;
14670    };
14671    #[cfg(any(test, feature = "test-support"))]
14672    if let Some(cpp) = analyzer.cpp {
14673        cpp.record_cpp_class_strength_parse_for_test();
14674    }
14675    let mut parser = Parser::new();
14676    if parser
14677        .set_language(&tree_sitter_cpp::LANGUAGE.into())
14678        .is_err()
14679    {
14680        return CppClassDeclarationStrength::Unknown;
14681    }
14682    let Some(tree) = parser.parse(&source, None) else {
14683        return CppClassDeclarationStrength::Unknown;
14684    };
14685    // This branch reparses a file the analyzer has no prepared tree for, so its
14686    // recovery index is that one tree's and cannot be shared.
14687    let recovered_export_classes =
14688        CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
14689    cpp_class_declaration_strength_in_tree(
14690        analyzer,
14691        &recovered_export_classes,
14692        candidate,
14693        &source,
14694        tree.root_node(),
14695    )
14696}
14697
14698fn cpp_class_declaration_strength_in_tree(
14699    analyzer: &CppGraphSource<'_>,
14700    recovered_export_classes: &CppRecoveredExportClassIndex,
14701    candidate: &CodeUnit,
14702    source: &str,
14703    root: Node<'_>,
14704) -> CppClassDeclarationStrength {
14705    let ranges = analyzer.ranges(candidate);
14706    let mut saw_forward = false;
14707    for range in ranges {
14708        // The recovered export-macro shapes answer for their own ranges; only a
14709        // range no recovery claims is read as a plain specifier.
14710        match recovered_class_body_at(
14711            recovered_export_classes,
14712            root,
14713            source,
14714            candidate.identifier(),
14715            &range,
14716        ) {
14717            Some(true) => return CppClassDeclarationStrength::Full,
14718            Some(false) => {
14719                saw_forward = true;
14720                continue;
14721            }
14722            None => {}
14723        }
14724        // Only a node covering the range's start byte can be the specifier for
14725        // this range, so apply that test where nodes enter the stack rather
14726        // than where they leave it. Pushing first meant one ask enqueued every
14727        // sibling at every level it descended, which on a translation unit with
14728        // thousands of top-level declarations is a per-ask cost proportional to
14729        // the file (#1496).
14730        let covers_range_start = |node: &Node<'_>| {
14731            node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
14732        };
14733        let mut stack = Vec::new();
14734        if covers_range_start(&root) {
14735            stack.push(root);
14736        }
14737        while let Some(node) = stack.pop() {
14738            if node.start_byte() == range.start_byte
14739                && node.end_byte() == range.end_byte
14740                && matches!(
14741                    node.kind(),
14742                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14743                )
14744            {
14745                if cpp_class_node_has_body(node) {
14746                    return CppClassDeclarationStrength::Full;
14747                }
14748                saw_forward = true;
14749            }
14750            let mut cursor = node.walk();
14751            stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
14752        }
14753    }
14754    if saw_forward {
14755        CppClassDeclarationStrength::Forward
14756    } else {
14757        CppClassDeclarationStrength::Unknown
14758    }
14759}
14760
14761fn cpp_class_node_has_body(node: Node<'_>) -> bool {
14762    node.child_by_field_name("body").is_some() || {
14763        let mut cursor = node.walk();
14764        node.named_children(&mut cursor).any(|child| {
14765            matches!(
14766                child.kind(),
14767                "declaration_list" | "field_declaration_list" | "enumerator_list"
14768            )
14769        })
14770    }
14771}
14772
14773pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
14774    if !code_unit.owner_is_type_scope() {
14775        return None;
14776    }
14777    let owner_fq = code_unit.fq().parent()?;
14778    ctx.analyzer
14779        .workspace_definitions()
14780        .exact(&owner_fq)
14781        .into_iter()
14782        .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
14783}
14784
14785pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
14786    left.kind() == right.kind()
14787        && left.fq_name() == right.fq_name()
14788        && left.signature() == right.signature()
14789        && left.source() == right.source()
14790}
14791
14792pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
14793    same_symbol(left, right) || same_logical_symbol(left, right)
14794}
14795
14796pub fn same_visible_global_field_symbol(
14797    analyzer: &CppGraphSource<'_>,
14798    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
14799    left: &CodeUnit,
14800    right: &CodeUnit,
14801) -> bool {
14802    if same_symbol(left, right) {
14803        return true;
14804    }
14805    if !same_logical_symbol(left, right) {
14806        return false;
14807    }
14808    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
14809        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
14810    {
14811        left.source() == right.source()
14812    } else {
14813        true
14814    }
14815}
14816
14817fn cpp_global_field_has_internal_linkage_cached(
14818    analyzer: &CppGraphSource<'_>,
14819    cache: &mut HashMap<CodeUnit, bool>,
14820    candidate: &CodeUnit,
14821) -> bool {
14822    if let Some(internal) = cache.get(candidate) {
14823        return *internal;
14824    }
14825    #[cfg(any(test, feature = "test-support"))]
14826    note_cpp_global_field_internal_linkage_classification_for_test();
14827    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
14828    cache.insert(candidate.clone(), internal);
14829    internal
14830}
14831
14832#[cfg(any(test, feature = "test-support"))]
14833thread_local! {
14834    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
14835}
14836
14837#[cfg(any(test, feature = "test-support"))]
14838fn note_cpp_global_field_internal_linkage_classification_for_test() {
14839    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
14840        count.set(count.get() + 1);
14841    });
14842}
14843
14844#[cfg(any(test, feature = "test-support"))]
14845pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
14846    body: impl FnOnce() -> T,
14847) -> (T, usize) {
14848    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
14849        count.set(0);
14850        let result = body();
14851        let observed = count.get();
14852        count.set(0);
14853        (result, observed)
14854    })
14855}
14856
14857pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
14858    left.kind() == right.kind()
14859        && left.fq_name() == right.fq_name()
14860        && left.signature() == right.signature()
14861}
14862
14863pub fn cpp_global_field_has_internal_linkage(
14864    analyzer: &CppGraphSource<'_>,
14865    candidate: &CodeUnit,
14866) -> bool {
14867    if !candidate.is_field() || candidate.short_name().contains('.') {
14868        return false;
14869    }
14870    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
14871        return false;
14872    };
14873    match local_linkage {
14874        CppFieldLinkage::Internal => true,
14875        CppFieldLinkage::External => false,
14876        CppFieldLinkage::InternalUnlessExternalPeer => {
14877            !cpp_global_field_linkage_peers(analyzer, candidate)
14878                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
14879                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
14880        }
14881    }
14882}
14883
14884fn cpp_global_field_linkage_peers<'a>(
14885    analyzer: &CppGraphSource<'a>,
14886    candidate: &'a CodeUnit,
14887) -> impl Iterator<Item = CodeUnit> + 'a {
14888    let name = candidate.fq().clone();
14889    analyzer
14890        .workspace_definitions()
14891        .exact(&name)
14892        .into_iter()
14893        .filter(move |peer| {
14894            if peer == candidate {
14895                return false;
14896            }
14897            #[cfg(any(test, feature = "test-support"))]
14898            note_cpp_global_field_linkage_peer_inspection_for_test();
14899            same_logical_symbol(peer, candidate)
14900        })
14901}
14902
14903#[cfg(any(test, feature = "test-support"))]
14904thread_local! {
14905    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
14906}
14907
14908#[cfg(any(test, feature = "test-support"))]
14909fn note_cpp_global_field_linkage_peer_inspection_for_test() {
14910    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
14911        count.set(count.get() + 1);
14912    });
14913}
14914
14915#[cfg(any(test, feature = "test-support"))]
14916pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
14917    body: impl FnOnce() -> T,
14918) -> (T, usize) {
14919    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
14920        count.set(0);
14921        let result = body();
14922        let observed = count.get();
14923        count.set(0);
14924        (result, observed)
14925    })
14926}
14927
14928fn cpp_global_field_declaration_linkage(
14929    analyzer: &CppGraphSource<'_>,
14930    candidate: &CodeUnit,
14931) -> Option<CppFieldLinkage> {
14932    if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
14933        return Some(linkage);
14934    }
14935    let cpp = analyzer.cpp?;
14936    if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
14937        return cpp_global_field_declaration_linkage_in_tree(
14938            analyzer,
14939            candidate,
14940            prepared.source(),
14941            prepared.tree().root_node(),
14942        );
14943    }
14944    let source = analyzer.indexed_source(candidate.source())?;
14945    let mut parser = Parser::new();
14946    if parser
14947        .set_language(&tree_sitter_cpp::LANGUAGE.into())
14948        .is_err()
14949    {
14950        return None;
14951    }
14952    let tree = parser.parse(&source, None)?;
14953    cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
14954}
14955
14956fn cpp_global_field_declaration_linkage_in_tree(
14957    analyzer: &CppGraphSource<'_>,
14958    candidate: &CodeUnit,
14959    source: &str,
14960    root: Node<'_>,
14961) -> Option<CppFieldLinkage> {
14962    analyzer.ranges(candidate).iter().find_map(|range| {
14963        node_for_exact_range(root, range)
14964            .and_then(enclosing_cpp_field_declaration)
14965            .map(|declaration| {
14966                // One question about one declaration; see `ParentIndex::unindexed`.
14967                cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
14968            })
14969    })
14970}
14971
14972fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
14973    loop {
14974        if matches!(node.kind(), "declaration" | "field_declaration") {
14975            return Some(node);
14976        }
14977        node = node.parent()?;
14978    }
14979}
14980
14981#[cfg(test)]
14982mod tests {
14983    use super::*;
14984
14985    #[test]
14986    fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
14987        let source = "int size(void) { return sizeof(((Payload))); }\n";
14988        let mut parser = Parser::new();
14989        parser
14990            .set_language(&tree_sitter_cpp::LANGUAGE.into())
14991            .expect("C++ grammar");
14992        let tree = parser.parse(source, None).expect("fixture tree");
14993        let start = source.find("Payload").expect("sizeof operand");
14994        let node = tree
14995            .root_node()
14996            .named_descendant_for_byte_range(start, start + "Payload".len())
14997            .expect("focused operand");
14998        let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
14999        let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
15000
15001        assert_eq!(node.kind(), "identifier");
15002        assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
15003        assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
15004    }
15005
15006    #[test]
15007    fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
15008        let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
15009        assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
15010        assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
15011        assert!(indexed_namespace_path_is_recoverable(
15012            &["cache".to_string()],
15013            &indexed,
15014            1,
15015        ));
15016    }
15017
15018    #[test]
15019    fn sort_lookup_units_totally_orders_every_identity_field() {
15020        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
15021        let base = CodeUnit::with_signature(
15022            file.clone(),
15023            CodeUnitType::Function,
15024            "scope",
15025            "value",
15026            Some("()".to_string()),
15027            false,
15028        );
15029        let different_kind = CodeUnit::with_signature(
15030            file.clone(),
15031            CodeUnitType::Field,
15032            "scope",
15033            "value",
15034            Some("()".to_string()),
15035            false,
15036        );
15037        let synthetic = base.with_synthetic(true);
15038
15039        let interner = segment_interner();
15040        let mut member_fq = FqName::new();
15041        member_fq.push(interner.intern("scope", SegmentKind::Package));
15042        member_fq.push(interner.intern("value", SegmentKind::Member));
15043        let different_package_boundary = CodeUnit::from_fq(
15044            file.clone(),
15045            CodeUnitType::Function,
15046            member_fq,
15047            0,
15048            Some("()".to_string()),
15049            false,
15050        );
15051
15052        let mut unknown_fq = FqName::new();
15053        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
15054        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
15055        let different_segment_kind = CodeUnit::from_fq(
15056            file,
15057            CodeUnitType::Function,
15058            unknown_fq,
15059            1,
15060            Some("()".to_string()),
15061            false,
15062        );
15063
15064        let input = vec![
15065            base,
15066            different_kind,
15067            synthetic,
15068            different_package_boundary,
15069            different_segment_kind,
15070        ];
15071        let mut expected = input.clone();
15072        sort_lookup_units(&mut expected);
15073        assert!(expected.windows(2).all(|pair| {
15074            let mut ordered = pair.to_vec();
15075            sort_lookup_units(&mut ordered);
15076            ordered == pair && pair[0] != pair[1]
15077        }));
15078
15079        let mut reversed = input.clone();
15080        reversed.reverse();
15081        sort_lookup_units(&mut reversed);
15082        assert_eq!(reversed, expected);
15083
15084        let mut rotated = input;
15085        rotated.rotate_left(2);
15086        sort_lookup_units(&mut rotated);
15087        assert_eq!(rotated, expected);
15088    }
15089
15090    #[test]
15091    fn displaced_preprocessor_terminator_bounds_the_real_guard() {
15092        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";
15093        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
15094        let parse = |source: &str| {
15095            let mut parser = Parser::new();
15096            parser
15097                .set_language(&tree_sitter_cpp::LANGUAGE.into())
15098                .expect("C++ grammar");
15099            parser.parse(source, None).expect("fixture tree")
15100        };
15101
15102        let tree = parse(damaged);
15103        let root = tree.root_node();
15104        let target = damaged.find("target").expect("target byte");
15105        let declaration = root
15106            .descendant_for_byte_range(target, target + "target".len())
15107            .and_then(|mut node| {
15108                loop {
15109                    if node.kind() == "declaration" {
15110                        break Some(node);
15111                    }
15112                    node = node.parent()?;
15113                }
15114            })
15115            .expect("declaration after the displaced terminator");
15116        let conditional = declaration
15117            .parent()
15118            .filter(|node| node.kind() == "preproc_ifdef")
15119            .expect("damaged inner conditional");
15120        let outer = conditional
15121            .parent()
15122            .filter(|node| node.kind() == "preproc_ifdef")
15123            .expect("ordinary outer include guard");
15124        let terminator = cpp_displaced_preprocessor_terminator(conditional)
15125            .expect("structured displaced #endif");
15126        assert_eq!(node_text(terminator, damaged), "#endif");
15127        assert!(terminator.end_byte() <= declaration.start_byte());
15128        assert!(!preprocessor_conditional_contains_descendant(
15129            conditional,
15130            declaration
15131        ));
15132        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
15133        assert!(preprocessor_conditional_contains_descendant(
15134            outer,
15135            declaration
15136        ));
15137
15138        let tree = parse(guarded);
15139        let conditional = tree
15140            .root_node()
15141            .named_child(0)
15142            .filter(|node| node.kind() == "preproc_ifdef")
15143            .expect("ordinary conditional");
15144        let declaration = conditional
15145            .named_children(&mut conditional.walk())
15146            .find(|node| node.kind() == "declaration")
15147            .expect("guarded declaration");
15148        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
15149        assert!(preprocessor_conditional_contains_descendant(
15150            conditional,
15151            declaration
15152        ));
15153
15154        let damaged_alternative = format!(
15155            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
15156            "UNUSED(value)\n".repeat(64)
15157        );
15158        let tree = parse(&damaged_alternative);
15159        let conditional = tree
15160            .root_node()
15161            .named_child(0)
15162            .filter(|node| node.kind() == "preproc_ifdef")
15163            .expect("outer conditional with an alternative");
15164        assert!(conditional.has_error());
15165        assert!(conditional.child_by_field_name("alternative").is_some());
15166        assert!(
15167            conditional
15168                .child(conditional.child_count() - 1)
15169                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
15170        );
15171        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
15172
15173        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";
15174        let tree = parse(split_declaration);
15175        let root = tree.root_node();
15176        let conditional = root
15177            .named_children(&mut root.walk())
15178            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
15179            .expect("split declaration conditional");
15180        let target = split_declaration
15181            .find("static int target")
15182            .expect("target byte");
15183        let boundary =
15184            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
15185        assert!(boundary.end_byte <= target, "{boundary:?}");
15186        assert_eq!(boundary.end_line, 9, "{boundary:?}");
15187        let target_node = root
15188            .descendant_for_byte_range(target, target + "static".len())
15189            .expect("target node");
15190        assert!(!preprocessor_conditional_contains_descendant(
15191            conditional,
15192            target_node
15193        ));
15194    }
15195
15196    #[test]
15197    fn fragmented_reference_guard_is_recovered() {
15198        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";
15199        let mut parser = Parser::new();
15200        parser
15201            .set_language(&tree_sitter_cpp::LANGUAGE.into())
15202            .expect("C++ grammar");
15203        let tree = parser.parse(source, None).expect("fixture tree");
15204        let start = source.rfind("helper").expect("reference byte");
15205        let node = tree
15206            .root_node()
15207            .descendant_for_byte_range(start, start + "helper".len())
15208            .expect("reference node");
15209        let mut expected = HashSet::default();
15210        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
15211            vec![
15212                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
15213                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
15214            ],
15215        )));
15216        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
15217    }
15218
15219    #[test]
15220    fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
15221        let source = r#"#ifdef _WIN32
15222#if defined(__cplusplus)
15223extern "C"
15224#endif
15225int platform_api(void);
15226#endif
15227
15228#ifdef _WIN32
15229static int entropy_target(void) { return 0; }
15230#else
15231#ifdef HAVE_COMMON_RANDOM
15232static int other_target(void) { return 0; }
15233#elif defined(HAVE_GETENTROPY)
15234static int entropy_target(void) { return 1; }
15235static int use_entropy(void) { return entropy_target(); }
15236#endif
15237#endif
15238"#;
15239        let mut parser = Parser::new();
15240        parser
15241            .set_language(&tree_sitter_cpp::LANGUAGE.into())
15242            .expect("C++ grammar");
15243        let tree = parser.parse(source, None).expect("fixture tree");
15244        let start = source.rfind("entropy_target()").expect("reference");
15245        let node = tree
15246            .root_node()
15247            .descendant_for_byte_range(start, start + "entropy_target".len())
15248            .expect("reference node");
15249        let guards = preprocessor_guard_environment(node, source).expect("active C branch");
15250        assert!(
15251            guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
15252            "{guards:#?}"
15253        );
15254        assert!(
15255            guards.contains(&PreprocessorGuard::Undefined(
15256                "HAVE_COMMON_RANDOM".to_string()
15257            )),
15258            "{guards:#?}"
15259        );
15260        assert!(
15261            guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
15262            "{guards:#?}"
15263        );
15264        assert!(
15265            !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
15266            "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
15267        );
15268    }
15269
15270    #[test]
15271    fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
15272        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";
15273        let mut parser = Parser::new();
15274        parser
15275            .set_language(&tree_sitter_cpp::LANGUAGE.into())
15276            .expect("C++ grammar");
15277        let tree = parser.parse(source, None).expect("fixture tree");
15278        let root = tree.root_node();
15279        let node_at = |text: &str, start: usize| {
15280            root.descendant_for_byte_range(start, start + text.len())
15281                .expect("token node")
15282        };
15283
15284        let key_start = source.find("case KEY").expect("case label") + "case ".len();
15285        let guard_start = source.find("ENABLE_KEYS").expect("guard name");
15286        assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
15287        assert!(!is_ordinary_macro_reference_node(node_at(
15288            "ENABLE_KEYS",
15289            guard_start,
15290        )));
15291    }
15292
15293    #[test]
15294    fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
15295        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";
15296        let mut parser = Parser::new();
15297        parser
15298            .set_language(&tree_sitter_cpp::LANGUAGE.into())
15299            .expect("C++ grammar");
15300        let tree = parser.parse(source, None).expect("fixture tree");
15301        let root = tree.root_node();
15302        let definition_start = source.find("target(void)").expect("definition");
15303        let reference_start = source.rfind("target()").expect("reference");
15304        let definition = root
15305            .descendant_for_byte_range(definition_start, definition_start + "target".len())
15306            .expect("definition node");
15307        let reference = root
15308            .descendant_for_byte_range(reference_start, reference_start + "target".len())
15309            .expect("reference node");
15310        let required =
15311            preprocessor_guard_environment(definition, source).expect("definition guard");
15312        let active = preprocessor_guard_environment(reference, source).expect("reference guard");
15313        assert!(guard_requirements_hold_at_reference(
15314            &required,
15315            Some(&active)
15316        ));
15317    }
15318
15319    #[test]
15320    fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
15321        let source = "g_autoptr(FuChunkArray) self = make_array();";
15322        let mut parser = Parser::new();
15323        parser
15324            .set_language(&tree_sitter_cpp::LANGUAGE.into())
15325            .expect("C++ grammar");
15326        let tree = parser.parse(source, None).expect("fixture tree");
15327        let statement = tree.root_node().named_child(0).expect("statement");
15328        let binding =
15329            recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
15330        assert_eq!(binding.name, "self");
15331        assert_eq!(binding.type_name, "FuChunkArray");
15332        assert_eq!(binding.pointer_depth, 1);
15333
15334        let near_miss = "holder(FuChunkArray) self = make_array();";
15335        let tree = parser.parse(near_miss, None).expect("near-miss tree");
15336        let statement = tree.root_node().named_child(0).expect("statement");
15337        assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
15338    }
15339
15340    #[test]
15341    fn boolean_guard_normalization_proves_equivalence_and_implication() {
15342        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
15343        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
15344        let negated_windows_branch =
15345            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
15346        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
15347        assert_eq!(negated_windows_branch, portable);
15348
15349        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
15350        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
15351        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
15352        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
15353        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
15354        assert!(fallback_branch.implies(&fallback_declaration));
15355        assert!(
15356            BooleanGuardExpression::Truthy("FEATURE".to_string())
15357                .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
15358        );
15359        assert!(
15360            BooleanGuardExpression::Undefined("FEATURE".to_string())
15361                .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
15362        );
15363        assert!(
15364            !BooleanGuardExpression::Defined("FEATURE".to_string())
15365                .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
15366        );
15367        assert!(!fallback_declaration.implies(&fallback_branch));
15368    }
15369
15370    #[test]
15371    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
15372        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";
15373        let mut parser = Parser::new();
15374        parser
15375            .set_language(&tree_sitter_cpp::LANGUAGE.into())
15376            .expect("C++ grammar");
15377        let tree = parser.parse(source, None).expect("fixture tree");
15378        let root = tree.root_node();
15379        let call = |marker: &str| {
15380            let start = source.find(marker).expect("call marker");
15381            let mut node = root
15382                .descendant_for_byte_range(start, start + "helper".len())
15383                .expect("call name node");
15384            loop {
15385                if node.kind() == "call_expression" {
15386                    break node;
15387                }
15388                node = node.parent().expect("call expression ancestor");
15389            }
15390        };
15391        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
15392        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
15393        let keyword_call = call("helper(NULL, template); /* bound */");
15394        let keyword_arguments = keyword_call
15395            .child_by_field_name("arguments")
15396            .expect("keyword argument list");
15397        assert_eq!(
15398            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
15399            1
15400        );
15401        assert_eq!(
15402            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
15403            0
15404        );
15405
15406        let unbound_call = call("helper(NULL, template); /* unbound */");
15407        let unbound_arguments = unbound_call
15408            .child_by_field_name("arguments")
15409            .expect("unbound argument list");
15410        assert_eq!(
15411            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
15412            0
15413        );
15414    }
15415
15416    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
15417        let mut parser = Parser::new();
15418        parser
15419            .set_language(&tree_sitter_cpp::LANGUAGE.into())
15420            .expect("C++ grammar");
15421        let tree = parser.parse(source, None).expect("C++ fixture tree");
15422        let mut stack = vec![tree.root_node()];
15423        while let Some(node) = stack.pop() {
15424            if node.kind() == "enum_specifier" {
15425                return flattened_macro_namespace_components(node, source);
15426            }
15427            let mut cursor = node.walk();
15428            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
15429            stack.extend(children.into_iter().rev());
15430        }
15431        None
15432    }
15433
15434    #[test]
15435    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
15436        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
15437namespace detail
15438{
15439enum class value_t { null };
15440}
15441NLOHMANN_JSON_NAMESPACE_END
15442NLOHMANN_JSON_NAMESPACE_BEGIN
15443namespace next
15444{
15445struct next_type {};
15446}
15447NLOHMANN_JSON_NAMESPACE_END
15448"#;
15449        assert_eq!(
15450            first_enum_flattened_namespace(complete),
15451            Some(vec!["detail".to_string()])
15452        );
15453
15454        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
15455        assert_eq!(
15456            first_enum_flattened_namespace(&stale_end),
15457            Some(vec!["detail".to_string()]),
15458            "a stale end marker before the begin marker must not replace the intended namespace"
15459        );
15460
15461        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
15462namespace detail
15463{
15464enum class value_t { null };
15465}
15466struct next_type {};
15467"#;
15468        assert_eq!(first_enum_flattened_namespace(incomplete), None);
15469    }
15470}
15471
15472/// Comparator laws for the total C++ lookup order introduced by #1876.
15473///
15474/// `sort_lookup_units` is the single tie-break the C++ resolver applies before
15475/// any "first wins" selection (template families in #1836, the visible
15476/// identifier index, the type-candidate lists). If its comparator is not a
15477/// total order over CodeUnit identity, some pair stays tied and the survivor
15478/// falls back to the order the units arrived in -- which is FxHash iteration
15479/// order over keys whose hash covers the absolute workspace root. That is the
15480/// exact mechanism behind #1836 and the #414 / #432 heisenbug, so the laws are
15481/// checked generatively rather than on one hand-picked list.
15482///
15483/// CodeUnit identity is `source`, `kind`, `fq`, `package_segment_count`,
15484/// `signature` and `synthetic` (see `impl PartialEq for CodeUnit`); a CodeUnit
15485/// carries no range, so declaration ranges are covered by the workspace-level
15486/// property in `tests/suite_analyzers/determinism_properties.rs` instead.
15487#[cfg(test)]
15488mod lookup_order_properties {
15489    use super::*;
15490    use proptest::prelude::*;
15491
15492    /// Segment spellings the C++ extractor and the shared renderer actually
15493    /// produce, including the `$`-joined nested spellings and non-ASCII
15494    /// identifiers that a byte-wise comparison has to keep apart.
15495    const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
15496    const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
15497    /// Two roots so the order is pinned across workspaces as well as inside
15498    /// one: the root path is precisely the byte string that used to leak into
15499    /// iteration order.
15500    const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
15501    const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
15502    const KINDS: [CodeUnitType; 6] = [
15503        CodeUnitType::Class,
15504        CodeUnitType::Function,
15505        CodeUnitType::Field,
15506        CodeUnitType::Module,
15507        CodeUnitType::Macro,
15508        CodeUnitType::FileScope,
15509    ];
15510
15511    /// Where one unit sits relative to another under the comparator that
15512    /// `sort_lookup_units` owns.
15513    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
15514    enum ProbedOrder {
15515        Before,
15516        Tied,
15517        After,
15518        /// Both directions reported "strictly first": the comparator is not
15519        /// dual, and no sort over it can be order-independent.
15520        Contradictory,
15521    }
15522
15523    impl ProbedOrder {
15524        fn mirror(self) -> Self {
15525            match self {
15526                ProbedOrder::Before => ProbedOrder::After,
15527                ProbedOrder::After => ProbedOrder::Before,
15528                other => other,
15529            }
15530        }
15531
15532        /// -1 / 0 / +1, so transitivity reads as the `<= 0` law.
15533        fn signum(self) -> i8 {
15534            match self {
15535                ProbedOrder::Before => -1,
15536                ProbedOrder::Tied => 0,
15537                ProbedOrder::After => 1,
15538                ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
15539            }
15540        }
15541    }
15542
15543    /// Read the comparator through its only caller.
15544    ///
15545    /// `sort_lookup_units` is a stable sort, so for a two-element slice the
15546    /// output says exactly whether the comparator put the second element
15547    /// strictly first. Sorting both arrangements of one pair therefore reports
15548    /// the comparator's verdict in both directions, including the contradictory
15549    /// case a single sort would hide.
15550    fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
15551        if left == right {
15552            // A stable sort cannot distinguish two equal values, and `Equal` is
15553            // the only verdict a total order can give them.
15554            return ProbedOrder::Tied;
15555        }
15556        let mut forward = vec![left.clone(), right.clone()];
15557        sort_lookup_units(&mut forward);
15558        let mut backward = vec![right.clone(), left.clone()];
15559        sort_lookup_units(&mut backward);
15560        let left_first = backward[0] == *left;
15561        let right_first = forward[0] == *right;
15562        match (left_first, right_first) {
15563            (true, true) => ProbedOrder::Contradictory,
15564            (true, false) => ProbedOrder::Before,
15565            (false, true) => ProbedOrder::After,
15566            (false, false) => ProbedOrder::Tied,
15567        }
15568    }
15569
15570    /// `(kind, text)` per segment. `CodeUnit`'s own `Debug` prints interned
15571    /// segment IDs, which are process-local and say nothing about a failure.
15572    fn fq_segments(unit: &CodeUnit) -> Vec<(&'static str, &'static str)> {
15573        let interner = segment_interner();
15574        unit.fq()
15575            .segments()
15576            .iter()
15577            .map(|&id| {
15578                let (text, kind) = interner.resolve(id);
15579                (kind.name(), text)
15580            })
15581            .collect()
15582    }
15583
15584    fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
15585        (
15586            0..ROOT_NAMES.len(),
15587            0..REL_PATHS.len(),
15588            0..KINDS.len(),
15589            prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
15590            0..3usize,
15591            0..SIGNATURES.len(),
15592            any::<bool>(),
15593        )
15594            .prop_map(
15595                |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
15596                    let source = ProjectFile::new(
15597                        std::env::temp_dir().join(ROOT_NAMES[root]),
15598                        REL_PATHS[rel_path],
15599                    );
15600                    let interner = segment_interner();
15601                    let mut fq = FqName::new();
15602                    for (atom, segment_kind) in &segments {
15603                        fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
15604                    }
15605                    // `from_fq` requires a non-empty declaration tail.
15606                    let package_segment_count = package_prefix % fq.len();
15607                    CodeUnit::from_fq(
15608                        source,
15609                        KINDS[kind],
15610                        fq,
15611                        package_segment_count,
15612                        SIGNATURES[signature].map(str::to_string),
15613                        synthetic,
15614                    )
15615                },
15616            )
15617    }
15618
15619    proptest! {
15620        #![proptest_config(ProptestConfig::with_cases(256))]
15621
15622        /// Reflexivity and duality: a unit ties with itself, and no pair is
15623        /// strictly first in both directions.
15624        #[test]
15625        fn lookup_order_is_reflexive_and_dual(
15626            left in code_unit_strategy(),
15627            right in code_unit_strategy(),
15628        ) {
15629            prop_assert_eq!(
15630                probe_order(&left, &left),
15631                ProbedOrder::Tied,
15632                "a unit must tie with itself: {:?}",
15633                left
15634            );
15635            let forward = probe_order(&left, &right);
15636            prop_assert_ne!(
15637                forward,
15638                ProbedOrder::Contradictory,
15639                "comparator put each of these strictly first: left={:?} right={:?}",
15640                left,
15641                right
15642            );
15643            prop_assert_eq!(
15644                probe_order(&right, &left),
15645                forward.mirror(),
15646                "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
15647                left,
15648                right
15649            );
15650        }
15651
15652        /// Transitivity: `a <= b` and `b <= c` imply `a <= c`.
15653        #[test]
15654        fn lookup_order_is_transitive(
15655            a in code_unit_strategy(),
15656            b in code_unit_strategy(),
15657            c in code_unit_strategy(),
15658        ) {
15659            let ab = probe_order(&a, &b);
15660            let bc = probe_order(&b, &c);
15661            let ac = probe_order(&a, &c);
15662            for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
15663                prop_assert_ne!(
15664                    probed,
15665                    ProbedOrder::Contradictory,
15666                    "comparator is not dual over {}: a={:?} b={:?} c={:?}",
15667                    pair,
15668                    a,
15669                    b,
15670                    c
15671                );
15672            }
15673            if ab.signum() <= 0 && bc.signum() <= 0 {
15674                prop_assert!(
15675                    ac.signum() <= 0,
15676                    "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
15677                     a={:?} b={:?} c={:?}",
15678                    ab,
15679                    bc,
15680                    ac,
15681                    a,
15682                    b,
15683                    c
15684                );
15685            }
15686        }
15687
15688        /// The property #1876 exists for: only identical identities may tie.
15689        /// A tie between distinct units is the residual hash-order dependence.
15690        #[test]
15691        fn lookup_order_separates_distinct_identities(
15692            left in code_unit_strategy(),
15693            right in code_unit_strategy(),
15694        ) {
15695            if probe_order(&left, &right) == ProbedOrder::Tied {
15696                prop_assert_eq!(
15697                    &left,
15698                    &right,
15699                    "distinct identities tied, so their order is whatever order they \
15700                     arrived in: left_segments={:?} right_segments={:?}",
15701                    fq_segments(&left),
15702                    fq_segments(&right)
15703                );
15704            }
15705        }
15706
15707        /// The consequence the resolver relies on: the sorted list is a
15708        /// function of the SET of units, not of the order they were pushed in.
15709        #[test]
15710        fn lookup_sort_is_permutation_invariant(
15711            units in prop::collection::vec(code_unit_strategy(), 1..=8),
15712        ) {
15713            let mut sorted = units.clone();
15714            sort_lookup_units(&mut sorted);
15715            for rotation in 0..units.len() {
15716                for reversed in [false, true] {
15717                    let mut permuted = units.clone();
15718                    permuted.rotate_left(rotation);
15719                    if reversed {
15720                        permuted.reverse();
15721                    }
15722                    sort_lookup_units(&mut permuted);
15723                    prop_assert_eq!(
15724                        &permuted,
15725                        &sorted,
15726                        "sorting a permutation gave a different list \
15727                         (rotation={}, reversed={}): input={:?}",
15728                        rotation,
15729                        reversed,
15730                        units
15731                    );
15732                }
15733            }
15734        }
15735    }
15736}