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};
4#[cfg(test)]
5use crate::declarations::cpp_displaced_preprocessor_terminator;
6use crate::declarations::{
7    cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_field_declaration_linkage,
8    cpp_template_term, node_text, normalize_cpp_whitespace, recovered_exported_class_has_body,
9    recovered_fragmented_plain_class_has_body,
10};
11use crate::graph::CppGraphSource;
12use crate::graph::extractor::ScanCtx;
13use crate::graph_support::CppSource;
14use crate::imports::{
15    IncludeTargetIndex, include_paths as cpp_include_paths, resolve_include_targets_with_index,
16};
17use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
18use brokk_bifrost_core::analyzer::model::{
19    CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
20    CppTemplateParameterMetadata, CppTemplateTerm,
21};
22use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
23use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
24use brokk_bifrost_core::analyzer::tree_walk::node_for_exact_range;
25use brokk_bifrost_core::analyzer::usages::common::same_node;
26use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
27use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
28use brokk_bifrost_core::cancellation::CancellationToken;
29use brokk_bifrost_core::hash::{HashMap, HashSet};
30use std::borrow::Cow;
31#[cfg(any(test, feature = "test-support"))]
32use std::cell::Cell;
33use std::cell::OnceCell;
34use std::cmp::Ordering as CmpOrdering;
35use std::collections::BTreeSet;
36use std::hash::Hash;
37#[cfg(any(test, feature = "test-support"))]
38use std::sync::atomic::{AtomicUsize, Ordering};
39use std::sync::{Arc, Mutex, OnceLock, RwLock};
40use std::thread::ThreadId;
41use tree_sitter::{Node, Parser, Tree};
42
43#[derive(Clone, Copy, PartialEq, Eq)]
44pub enum TargetKind {
45    Type,
46    Constructor,
47    FreeFunction,
48    Method,
49    GlobalField,
50    MemberField,
51    Macro,
52}
53
54pub enum LexicalTypeResolution {
55    Resolved {
56        unit: CodeUnit,
57        components: Vec<String>,
58        candidates: Vec<CodeUnit>,
59    },
60    Ambiguous,
61    Missing,
62}
63
64#[derive(Clone, Copy)]
65enum TypeCandidateResolution<'a> {
66    Canonical,
67    PreserveAlias,
68    PreserveTarget(&'a CodeUnit),
69}
70
71/// Why a name did not reduce to one indexed type declaration.
72///
73/// The two answers are not interchangeable. `Ambiguous` means the index holds
74/// several declarations and the caller must choose; `Unresolvable` means the
75/// index holds none, which is a boundary the workspace cannot see past. A
76/// `using`/`typedef` alias to a template parameter or to a standard-library
77/// type is unresolvable, and reporting it as ambiguity produced an `ambiguous`
78/// answer with an empty candidate list (#1828).
79#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80enum TypeCandidateFailure {
81    Ambiguous,
82    Unresolvable,
83}
84
85impl TypeCandidateFailure {
86    fn lexical_resolution(self) -> LexicalTypeResolution {
87        match self {
88            Self::Ambiguous => LexicalTypeResolution::Ambiguous,
89            Self::Unresolvable => LexicalTypeResolution::Missing,
90        }
91    }
92}
93
94pub enum LexicalCallableValueResolution {
95    Type(CodeUnit),
96    FreeFunction(CodeUnit),
97    Ambiguous,
98    Missing,
99}
100
101pub enum UsingEnumMemberResolution {
102    Resolved { owner: CodeUnit, member: CodeUnit },
103    Ambiguous,
104    Missing,
105}
106
107pub enum NamespaceValueResolution {
108    Resolved,
109    Ambiguous,
110    Missing,
111}
112
113#[derive(Clone, Debug, PartialEq, Eq)]
114pub enum OrdinaryMacroReferenceResolution {
115    Resolved(CodeUnit),
116    Ambiguous,
117    Missing,
118}
119
120#[derive(Clone, Debug, PartialEq, Eq)]
121pub enum RecoveredCReferenceRanges {
122    Complete(Vec<Range>),
123    LimitExceeded,
124}
125
126pub fn resolve_namespace_value(
127    analyzer: &CppGraphSource<'_>,
128    visibility: &VisibilityIndex<'_>,
129    file: &ProjectFile,
130    namespace: &str,
131    name: &str,
132    before_byte: usize,
133) -> NamespaceValueResolution {
134    let mut matches = Vec::new();
135    for candidate in visibility.visible_identifier_candidates(file, name) {
136        if type_owner_of(analyzer, candidate).is_some()
137            || candidate.package_name() != namespace
138            || (candidate.source() == file
139                && !analyzer
140                    .ranges(candidate)
141                    .iter()
142                    .any(|range| range.start_byte < before_byte))
143            || matches
144                .iter()
145                .any(|existing| same_visible_symbol(existing, candidate))
146        {
147            continue;
148        }
149        matches.push(candidate.clone());
150        if matches.len() > 1 {
151            return NamespaceValueResolution::Ambiguous;
152        }
153    }
154    matches
155        .pop()
156        .map(|_| NamespaceValueResolution::Resolved)
157        .unwrap_or(NamespaceValueResolution::Missing)
158}
159
160pub(crate) struct ScopedUsingEnumOwners {
161    scopes: Vec<Vec<CodeUnit>>,
162}
163
164/// Same-file class and namespace imports collected by the targeted scanner's AST prepass.
165/// Cross-file and inherited class imports are deliberately not inferred without persisted
166/// evidence; a missing imported enumerator therefore remains unproven rather than being
167/// misresolved.
168pub(crate) struct SemanticUsingEnumOwners {
169    class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
170    namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
171}
172
173pub(crate) enum SemanticUsingEnumMemberResolution {
174    Class(UsingEnumMemberResolution),
175    Namespace(UsingEnumMemberResolution),
176    Missing,
177}
178
179impl SemanticUsingEnumOwners {
180    pub(crate) fn new() -> Self {
181        Self {
182            class_imports: HashMap::default(),
183            namespace_imports: HashMap::default(),
184        }
185    }
186
187    pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
188        let imports = self.class_imports.entry(class).or_default();
189        if !imports
190            .iter()
191            .any(|existing| same_visible_symbol(existing, &enum_owner))
192        {
193            imports.push(enum_owner);
194        }
195    }
196
197    pub fn import_namespace(
198        &mut self,
199        namespace: Vec<String>,
200        declaration_byte: usize,
201        enum_owner: CodeUnit,
202    ) {
203        let imports = self.namespace_imports.entry(namespace).or_default();
204        if !imports
205            .iter()
206            .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
207        {
208            imports.push((declaration_byte, enum_owner));
209        }
210    }
211
212    pub fn resolve_member(
213        &self,
214        visibility: &VisibilityIndex<'_>,
215        file: &ProjectFile,
216        class: Option<&CodeUnit>,
217        namespace: &[String],
218        before_byte: usize,
219        name: &str,
220    ) -> SemanticUsingEnumMemberResolution {
221        if let Some(class) = class
222            && let Some((_, imports)) = self
223                .class_imports
224                .iter()
225                .find(|(owner, _)| same_visible_symbol(owner, class))
226        {
227            let resolution =
228                resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
229            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
230                return SemanticUsingEnumMemberResolution::Class(resolution);
231            }
232        }
233        for prefix_len in (0..=namespace.len()).rev() {
234            let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
235                continue;
236            };
237            let owners = imports
238                .iter()
239                .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
240                .map(|(_, owner)| owner);
241            let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
242            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
243                return SemanticUsingEnumMemberResolution::Namespace(resolution);
244            }
245        }
246        SemanticUsingEnumMemberResolution::Missing
247    }
248}
249
250fn resolve_using_enum_member_for_owners<'a>(
251    visibility: &VisibilityIndex<'_>,
252    file: &ProjectFile,
253    owners: impl IntoIterator<Item = &'a CodeUnit>,
254    name: &str,
255) -> UsingEnumMemberResolution {
256    let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
257    for owner in owners {
258        for member in visibility.visible_members_for_owner_name(file, owner, name) {
259            if !member.is_field()
260                || matches.iter().any(|(existing_owner, existing_member)| {
261                    same_visible_symbol(existing_owner, owner)
262                        && same_visible_symbol(existing_member, member)
263                })
264            {
265                continue;
266            }
267            matches.push((owner.clone(), member.clone()));
268        }
269    }
270    match matches.len() {
271        0 => UsingEnumMemberResolution::Missing,
272        1 => {
273            let (owner, member) = matches.pop().expect("one using-enum match");
274            UsingEnumMemberResolution::Resolved { owner, member }
275        }
276        _ => UsingEnumMemberResolution::Ambiguous,
277    }
278}
279
280impl ScopedUsingEnumOwners {
281    pub(crate) fn new() -> Self {
282        Self {
283            scopes: vec![Vec::new()],
284        }
285    }
286
287    pub fn enter_scope(&mut self) {
288        self.scopes.push(Vec::new());
289    }
290
291    pub fn exit_scope(&mut self) {
292        if self.scopes.len() > 1 {
293            self.scopes.pop();
294        }
295    }
296
297    pub fn import(&mut self, owner: CodeUnit) {
298        let scope = self
299            .scopes
300            .last_mut()
301            .expect("using-enum scope stack is never empty");
302        if !scope
303            .iter()
304            .any(|existing| same_visible_symbol(existing, &owner))
305        {
306            scope.push(owner);
307        }
308    }
309
310    pub fn resolve_member(
311        &self,
312        visibility: &VisibilityIndex<'_>,
313        file: &ProjectFile,
314        name: &str,
315    ) -> UsingEnumMemberResolution {
316        for scope in self.scopes.iter().rev() {
317            let resolution =
318                resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
319            if !matches!(resolution, UsingEnumMemberResolution::Missing) {
320                return resolution;
321            }
322        }
323        UsingEnumMemberResolution::Missing
324    }
325}
326
327#[derive(Clone)]
328pub struct TargetSpec {
329    pub target: CodeUnit,
330    pub kind: TargetKind,
331    pub owner: Option<CodeUnit>,
332    pub member_name: String,
333    pub callable_arity: Option<CallableArity>,
334    pub activated_callable_arities: Vec<ActivatedCallableArity>,
335    pub param_types: Option<Vec<String>>,
336    pub enum_owner_kind: EnumOwnerKind,
337    pub owner_is_forward_declaration: bool,
338}
339
340#[derive(Clone, Copy)]
341pub struct ActivatedCallableArity {
342    pub activation_byte: usize,
343    pub arity: CallableArity,
344}
345
346#[derive(Debug, PartialEq, Eq, Hash)]
347pub struct TypeScanKey {
348    target: LogicalSymbolKey,
349    member_name: String,
350}
351
352#[derive(Clone, Debug, PartialEq, Eq, Hash)]
353struct LogicalSymbolKey {
354    kind: CodeUnitType,
355    fq_name: String,
356    signature: Option<String>,
357}
358
359struct ResolvedTypeOwner {
360    unit: CodeUnit,
361    is_forward_declaration: bool,
362}
363
364#[derive(Clone, Copy, PartialEq, Eq)]
365pub enum EnumOwnerKind {
366    Scoped,
367    Unscoped,
368    NonEnum,
369}
370
371impl TargetSpec {
372    pub fn type_scan_key(&self) -> Option<TypeScanKey> {
373        (self.kind == TargetKind::Type).then(|| TypeScanKey {
374            target: logical_symbol_key(&self.target),
375            member_name: self.member_name.clone(),
376        })
377    }
378
379    pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
380        if target.is_class() {
381            return Some(Self::new(
382                target.clone(),
383                TargetKind::Type,
384                Some(target.clone()),
385                target.identifier().to_string(),
386                None,
387                None,
388            ));
389        }
390
391        if target.is_field() {
392            // A namespace (module) is not a receiver: a namespace-scoped constant such as
393            // `example::DefaultPrefix` is referenced unqualified from inside the namespace and
394            // qualified from outside, exactly like a global. Treating a module owner as a
395            // member-field owner makes the receiver/owner-context match reject every valid
396            // reference, so resolve it as a global field instead.
397            let owner = type_owner_of(analyzer, target);
398            let kind = if owner.is_some() {
399                TargetKind::MemberField
400            } else {
401                TargetKind::GlobalField
402            };
403            let enum_owner_kind = owner
404                .as_ref()
405                .map(|owner| classify_enum_owner(analyzer, owner))
406                .unwrap_or(EnumOwnerKind::NonEnum);
407            let mut spec = Self::new(
408                target.clone(),
409                kind,
410                owner,
411                target.identifier().to_string(),
412                None,
413                None,
414            );
415            spec.enum_owner_kind = enum_owner_kind;
416            return Some(spec);
417        }
418
419        if target.is_function() {
420            // Free functions declared inside a namespace have a module owner; that namespace is
421            // not a call receiver, so resolve them as free functions rather than methods.
422            let owner_resolution = target_type_owner_resolution(analyzer, target);
423            let owner_is_forward_declaration = owner_resolution
424                .as_ref()
425                .is_some_and(|owner| owner.is_forward_declaration);
426            let owner = owner_resolution.map(|owner| owner.unit);
427            let kind = if owner.as_ref().is_some_and(|owner| {
428                target.identifier() == owner.identifier()
429                    || analyzer
430                        .cpp
431                        .and_then(|cpp| cpp.template_metadata(owner))
432                        .is_some_and(|metadata| metadata.primary_name == target.identifier())
433            }) {
434                TargetKind::Constructor
435            } else if owner.is_some() {
436                TargetKind::Method
437            } else {
438                TargetKind::FreeFunction
439            };
440            let mut spec = Self::new(
441                target.clone(),
442                kind,
443                owner,
444                target.identifier().to_string(),
445                Some(cpp_callable_arity(analyzer, target)),
446                target.signature().and_then(cpp_signature_param_types),
447            );
448            spec.owner_is_forward_declaration = owner_is_forward_declaration;
449            return Some(spec);
450        }
451
452        if target.is_macro() {
453            return Some(Self::new(
454                target.clone(),
455                TargetKind::Macro,
456                None,
457                target.identifier().to_string(),
458                None,
459                None,
460            ));
461        }
462
463        None
464    }
465
466    pub fn with_visible_callable_arities<'a>(
467        &'a self,
468        analyzer: &CppGraphSource<'_>,
469        cpp: &dyn CppSource,
470        visibility: &VisibilityIndex<'_>,
471        file: &ProjectFile,
472        prepared: &PreparedSyntaxTree,
473    ) -> Cow<'a, Self> {
474        let macro_parameter_arity =
475            visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
476        let activated_callable_arities =
477            visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
478        if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
479            return Cow::Borrowed(self);
480        }
481        let mut effective = self.clone();
482        if let Some(macro_parameter_arity) = macro_parameter_arity {
483            effective.callable_arity = Some(macro_parameter_arity);
484        }
485        effective.activated_callable_arities = activated_callable_arities;
486        Cow::Owned(effective)
487    }
488
489    pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
490        let base = self.callable_arity?;
491        Some(
492            self.activated_callable_arities
493                .iter()
494                .filter(|candidate| candidate.activation_byte <= byte)
495                .fold(base, |arity, candidate| {
496                    merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
497                }),
498        )
499    }
500
501    pub fn new(
502        target: CodeUnit,
503        kind: TargetKind,
504        owner: Option<CodeUnit>,
505        member_name: String,
506        callable_arity: Option<CallableArity>,
507        param_types: Option<Vec<String>>,
508    ) -> Self {
509        Self {
510            target,
511            kind,
512            owner,
513            member_name,
514            callable_arity,
515            activated_callable_arities: Vec::new(),
516            param_types,
517            enum_owner_kind: EnumOwnerKind::NonEnum,
518            owner_is_forward_declaration: false,
519        }
520    }
521}
522
523fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
524    LogicalSymbolKey {
525        kind: unit.kind(),
526        fq_name: unit.fq_name(),
527        signature: unit.signature().map(str::to_string),
528    }
529}
530
531fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
532    let classify = |source: &str| {
533        let source = source.trim_start();
534        if source.starts_with("enum class ") || source.starts_with("enum struct ") {
535            Some(EnumOwnerKind::Scoped)
536        } else if source.starts_with("enum ") {
537            Some(EnumOwnerKind::Unscoped)
538        } else {
539            None
540        }
541    };
542    owner
543        .signature()
544        .and_then(classify)
545        .or_else(|| {
546            analyzer
547                .get_source(owner, false)
548                .as_deref()
549                .and_then(classify)
550        })
551        .unwrap_or(EnumOwnerKind::NonEnum)
552}
553
554#[derive(Clone, PartialEq, Eq, Hash)]
555pub struct CppScanBinding {
556    pub unit: Option<CodeUnit>,
557    pub type_name: Option<String>,
558    pub indirection: i32,
559}
560
561impl CppScanBinding {
562    pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
563        Self {
564            type_name: Some(cpp_name_for(&unit)),
565            unit: Some(unit),
566            indirection,
567        }
568    }
569
570    pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
571        Self {
572            type_name: Some(type_name),
573            unit,
574            indirection,
575        }
576    }
577
578    pub fn as_arg_type(&self) -> Option<CppArgType> {
579        let name = self
580            .type_name
581            .clone()
582            .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
583        Some(CppArgType {
584            name,
585            unit: self.unit.clone(),
586            indirection: self.indirection,
587            pointee_const: false,
588        })
589    }
590}
591
592type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
593type VisibleParserAliasTargetNamesCell = Arc<OnceLock<HashMap<String, HashSet<String>>>>;
594pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
595pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
596type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
597pub type MacroEnvironmentCursorCell = Arc<Mutex<MacroEnvironmentCursor>>;
598type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
599type MacroLocalBindingTemplateCache =
600    HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
601
602#[derive(Clone, Default)]
603pub struct MacroEnvironment {
604    bindings: HashMap<String, MacroBinding>,
605    unknown_names: bool,
606    applied_pragma_once_files: HashSet<ProjectFile>,
607    maybe_applied_pragma_once_files: HashSet<ProjectFile>,
608}
609
610#[derive(Default)]
611pub struct MacroEnvironmentCursor {
612    frontier: usize,
613    environment: Arc<MacroEnvironment>,
614}
615
616impl MacroEnvironment {
617    fn binding(&self, name: &str) -> Option<&MacroBinding> {
618        self.bindings.get(name)
619    }
620
621    fn may_bind(&self, name: &str) -> bool {
622        self.bindings.contains_key(name) || self.unknown_names
623    }
624
625    fn insert(&mut self, name: String, binding: MacroBinding) {
626        self.bindings.insert(name, binding);
627    }
628
629    fn remove(&mut self, name: &str) {
630        self.bindings.remove(name);
631    }
632
633    fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
634        for binding in self.bindings.values_mut() {
635            *binding = MacroBinding::uncertain_from(binding, source, byte);
636        }
637        self.unknown_names = true;
638    }
639}
640
641#[derive(Clone)]
642pub enum EffectiveUsingTarget {
643    Ordinary {
644        name: String,
645        target_components: Vec<String>,
646        global: bool,
647    },
648    Namespace {
649        namespace_components: Vec<String>,
650        global: bool,
651    },
652}
653
654#[derive(Clone)]
655pub struct OrdinaryTypeImport {
656    pub target: EffectiveUsingTarget,
657    pub source: ProjectFile,
658    pub declaration_byte: usize,
659    pub scope_start: usize,
660    pub scope_end: usize,
661    pub scope_depth: usize,
662    pub block_scope: bool,
663    pub lexical_depth: usize,
664    pub declaration_namespace: Vec<String>,
665    pub namespace_scope: Option<Vec<String>>,
666    pub resolved_target_components: Option<Vec<String>>,
667    pub required_guards: HashSet<PreprocessorGuard>,
668}
669
670#[derive(Clone)]
671pub struct ConditionalIncludeProjection {
672    pub activation_byte: usize,
673    pub required_guards: HashSet<PreprocessorGuard>,
674}
675
676#[derive(Default)]
677pub struct SourceUsingIndex {
678    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
679    pub directives: Vec<OrdinaryTypeImport>,
680}
681
682#[derive(Default)]
683pub struct ProjectUsingIndex {
684    pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
685    pub directives: Vec<OrdinaryTypeImport>,
686}
687
688type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
689
690pub struct EffectiveUsingIndex {
691    projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
692}
693
694impl EffectiveUsingIndex {
695    fn new(_root: ProjectFile) -> Self {
696        Self {
697            projected_by_name: Mutex::new(HashMap::default()),
698        }
699    }
700
701    pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
702        self.projected_by_name
703            .lock()
704            .expect("C++ effective-using projection cache poisoned")
705            .entry(name.to_string())
706            .or_default()
707            .clone()
708    }
709}
710
711pub enum OrdinaryTypeImportResolution {
712    Resolved {
713        target: CodeUnit,
714        target_components: Vec<String>,
715        lexical_depth: usize,
716        is_direct: bool,
717    },
718    Ambiguous {
719        lexical_depth: usize,
720    },
721    Missing,
722}
723
724type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
725type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
726type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
727type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
728type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
729type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
730type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
731
732/// Per-query C++ visibility facts.
733///
734/// The analyzer is *borrowed*, never cloned: `TreeSitterAnalyzer::clone` gives
735/// the clone a fresh, empty `QueryReadCache` on purpose (clones cross
736/// generations and overlays, where another generation's hydrated states would
737/// be wrong). An index that owned a clone would therefore see an inactive read
738/// cache for every `prepared_syntax` call it makes, re-reading and re-parsing
739/// the same source from the store once per candidate instead of once per query
740/// — the #1175 blow-up, where one scan re-parsed a 4.8 MB generated header
741/// tens of thousands of times.
742pub struct VisibilityIndex<'a> {
743    cpp: &'a dyn CppSource,
744    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
745    visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
746    global_field_internal_linkage: HashMap<CodeUnit, bool>,
747    visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
748    alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
749    visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
750    visible_parser_alias_target_names:
751        Mutex<HashMap<ProjectFile, VisibleParserAliasTargetNamesCell>>,
752    ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
753    project_using_index: OnceLock<ProjectUsingIndex>,
754    callable_reference_specs:
755        Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
756    include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
757    conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
758    #[cfg(any(test, feature = "test-support"))]
759    conditional_include_projection_index_build_count: AtomicUsize,
760    #[cfg(any(test, feature = "test-support"))]
761    conditional_include_projection_state_count: AtomicUsize,
762    #[cfg(any(test, feature = "test-support"))]
763    include_activation_build_count: AtomicUsize,
764    #[cfg(any(test, feature = "test-support"))]
765    using_donor_activation_count: AtomicUsize,
766    #[cfg(any(test, feature = "test-support"))]
767    using_namespace_lookup_count: AtomicUsize,
768    #[cfg(any(test, feature = "test-support"))]
769    using_name_candidate_inspection_count: AtomicUsize,
770    #[cfg(any(test, feature = "test-support"))]
771    callable_reference_spec_build_count: AtomicUsize,
772    #[cfg(any(test, feature = "test-support"))]
773    alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
774    #[cfg(any(test, feature = "test-support"))]
775    visible_parser_alias_name_set_build_count: AtomicUsize,
776    #[cfg(any(test, feature = "test-support"))]
777    visible_parser_alias_target_names_build_count: AtomicUsize,
778    field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
779    structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
780    indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
781    indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
782    precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
783    macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
784    pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
785    // A forward cursor is useful only while its caller visits one source in byte order. The
786    // authoritative differential shares this index across target workers, whose frontiers can
787    // interleave arbitrarily, so sharing one cursor per file would serialize the include replay
788    // and repeatedly reset it. Keep one bounded cursor per participating worker instead; the
789    // immutable event and parse caches above remain shared.
790    pub macro_environment_cursors:
791        Mutex<HashMap<(ProjectFile, ThreadId), MacroEnvironmentCursorCell>>,
792    macro_replacements: Mutex<MacroReplacementCache>,
793    macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
794    callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
795    #[cfg(any(test, feature = "test-support"))]
796    pub macro_replacement_parse_count: AtomicUsize,
797    #[cfg(any(test, feature = "test-support"))]
798    pub macro_event_application_count: AtomicUsize,
799    #[cfg(any(test, feature = "test-support"))]
800    pub macro_environment_copy_count: AtomicUsize,
801    cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
802    cpp_template_families: HashMap<String, Vec<CodeUnit>>,
803    #[cfg(any(test, feature = "test-support"))]
804    qualified_candidate_inspections: AtomicUsize,
805    #[cfg(any(test, feature = "test-support"))]
806    target_preserving_type_resolution_count: AtomicUsize,
807}
808
809#[derive(Clone, Debug, PartialEq, Eq, Hash)]
810pub enum PreprocessorGuard {
811    Defined(String),
812    Undefined(String),
813    Boolean(BooleanGuardExpression),
814    Expression(String),
815    NegatedExpression(String),
816    Constant(bool),
817}
818
819#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
820pub enum BooleanGuardExpression {
821    Defined(String),
822    Undefined(String),
823    Truthy(String),
824    Falsy(String),
825    Opaque(String),
826    NegatedOpaque(String),
827    All(Vec<BooleanGuardExpression>),
828    Any(Vec<BooleanGuardExpression>),
829    Constant(bool),
830}
831
832impl BooleanGuardExpression {
833    fn negated(&self) -> Self {
834        match self {
835            Self::Defined(name) => Self::Undefined(name.clone()),
836            Self::Undefined(name) => Self::Defined(name.clone()),
837            Self::Truthy(name) => Self::Falsy(name.clone()),
838            Self::Falsy(name) => Self::Truthy(name.clone()),
839            Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
840            Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
841            Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
842            Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
843            Self::Constant(value) => Self::Constant(!value),
844        }
845    }
846
847    fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
848        Self::normalized(expressions, true)
849    }
850
851    fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
852        Self::normalized(expressions, false)
853    }
854
855    fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
856        let mut normalized = Vec::new();
857        for expression in expressions {
858            match expression {
859                Self::All(nested) if conjunction => normalized.extend(nested),
860                Self::Any(nested) if !conjunction => normalized.extend(nested),
861                Self::Constant(value) if value == conjunction => {}
862                Self::Constant(value) => return Self::Constant(value),
863                expression => normalized.push(expression),
864            }
865        }
866        normalized.sort_unstable();
867        normalized.dedup();
868        match normalized.len() {
869            0 => Self::Constant(conjunction),
870            1 => normalized.pop().expect("one Boolean guard expression"),
871            _ if conjunction => Self::All(normalized),
872            _ => Self::Any(normalized),
873        }
874    }
875
876    fn implies(&self, required: &Self) -> bool {
877        if self == required
878            || matches!(self, Self::Constant(false))
879            || matches!(required, Self::Constant(true))
880        {
881            return true;
882        }
883        match self {
884            Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
885            Self::All(active) => match required {
886                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
887                _ => active.iter().any(|expression| expression.implies(required)),
888            },
889            _ => match required {
890                Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
891                Self::All(required) => required.iter().all(|expression| self.implies(expression)),
892                _ => false,
893            },
894        }
895    }
896
897    pub fn heap_size(&self) -> usize {
898        match self {
899            Self::Defined(value)
900            | Self::Undefined(value)
901            | Self::Truthy(value)
902            | Self::Falsy(value)
903            | Self::Opaque(value)
904            | Self::NegatedOpaque(value) => value.len(),
905            Self::All(expressions) | Self::Any(expressions) => {
906                expressions
907                    .iter()
908                    .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
909                        size.saturating_add(std::mem::size_of::<Self>())
910                            .saturating_add(expression.heap_size())
911                    })
912            }
913            Self::Constant(_) => 0,
914        }
915    }
916}
917
918impl PreprocessorGuard {
919    fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
920        match self {
921            Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
922            Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
923            Self::Boolean(expression) => Some(expression.clone()),
924            Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
925            Self::Expression(_) | Self::NegatedExpression(_) => None,
926        }
927    }
928
929    fn negated(&self) -> Self {
930        match self {
931            Self::Defined(name) => Self::Undefined(name.clone()),
932            Self::Undefined(name) => Self::Defined(name.clone()),
933            Self::Boolean(expression) => Self::Boolean(expression.negated()),
934            Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
935            Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
936            Self::Constant(value) => Self::Constant(!value),
937        }
938    }
939
940    fn may_depend_on_macro(&self, macro_name: &str) -> bool {
941        match self {
942            Self::Defined(name) | Self::Undefined(name) => name == macro_name,
943            // The expression has already been isolated structurally by
944            // tree-sitter, but its full preprocessor semantics are outside the
945            // analyzer's guard model. Any macro mutation can therefore change
946            // its truth value.
947            Self::Boolean(_) | Self::Expression(_) | Self::NegatedExpression(_) => true,
948            Self::Constant(_) => false,
949        }
950    }
951}
952
953#[derive(Clone, PartialEq, Eq)]
954pub enum MacroDefinition {
955    Object {
956        replacement: String,
957    },
958    Function {
959        parameters: Vec<String>,
960        replacement: String,
961    },
962    Unsupported,
963}
964
965#[derive(Clone, Debug, PartialEq, Eq)]
966pub enum MacroIncludeProtection {
967    MacroGuard(String),
968    PragmaOnce,
969    None,
970}
971
972enum ParsedMacroReplacement {
973    Parsed { source: String, tree: Tree },
974    Unsupported,
975}
976
977#[derive(Clone)]
978enum MacroLocalBindingTypeTemplate {
979    Parameter(usize),
980    Fixed(String),
981}
982
983#[derive(Clone)]
984struct MacroLocalBindingTemplate {
985    name: String,
986    declared_type: MacroLocalBindingTypeTemplate,
987    pointer_depth: i32,
988}
989
990/// A local declaration contributed by one structurally known function-like macro.
991///
992/// `type_node` points into the invocation syntax when the replacement's type
993/// is one of the macro parameters. Consumers can therefore use their normal
994/// lexical type resolver without parsing replacement text themselves.
995pub struct MacroLocalBinding<'tree> {
996    pub name: String,
997    pub type_name: String,
998    pub type_node: Option<Node<'tree>>,
999    pub pointer_depth: i32,
1000}
1001
1002#[derive(Clone, PartialEq, Eq)]
1003pub struct MacroBinding {
1004    source: ProjectFile,
1005    declaration_byte: usize,
1006    definition: MacroDefinition,
1007    exact: bool,
1008}
1009
1010impl MacroBinding {
1011    fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1012        Self {
1013            source: source.clone(),
1014            declaration_byte,
1015            definition: MacroDefinition::Unsupported,
1016            exact: false,
1017        }
1018    }
1019
1020    fn is_exact(&self) -> bool {
1021        self.exact
1022    }
1023
1024    fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1025        Self {
1026            source: source.clone(),
1027            declaration_byte,
1028            definition: current.definition.clone(),
1029            exact: false,
1030        }
1031    }
1032}
1033
1034#[derive(Clone)]
1035pub enum MacroEvent {
1036    Define {
1037        name: String,
1038        binding: MacroBinding,
1039        byte: usize,
1040        conditional: bool,
1041    },
1042    Undef {
1043        name: String,
1044        byte: usize,
1045        conditional: bool,
1046    },
1047    Include {
1048        targets: Vec<ProjectFile>,
1049        byte: usize,
1050        conditional: bool,
1051    },
1052    Invalidate {
1053        byte: usize,
1054    },
1055}
1056
1057impl MacroEvent {
1058    pub fn byte(&self) -> usize {
1059        match self {
1060            Self::Define { byte, .. }
1061            | Self::Undef { byte, .. }
1062            | Self::Include { byte, .. }
1063            | Self::Invalidate { byte } => *byte,
1064        }
1065    }
1066}
1067
1068#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1069pub enum CallArityEvidence {
1070    Exact(usize),
1071    Unknown,
1072}
1073
1074impl CallArityEvidence {
1075    pub fn exact(self) -> Option<usize> {
1076        match self {
1077            Self::Exact(arity) => Some(arity),
1078            Self::Unknown => None,
1079        }
1080    }
1081
1082    pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1083        self.exact().map(|arity| expected.accepts(arity))
1084    }
1085}
1086
1087#[derive(Clone)]
1088struct DeclaredFieldTypeFact {
1089    type_text: String,
1090    indirection: i32,
1091    template_arguments: Option<Vec<CppTemplateExpression>>,
1092}
1093
1094#[derive(Clone)]
1095enum StructuredAliasTarget {
1096    Builtin,
1097    Named {
1098        components: Vec<String>,
1099        global: bool,
1100        arguments: Option<Vec<CppTemplateExpression>>,
1101    },
1102}
1103
1104struct CppAlias {
1105    name: String,
1106    target: String,
1107    namespace: Option<String>,
1108}
1109
1110type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1111
1112/// Why template-argument resolution failed. Definition diagnostics render
1113/// each mode differently; graph scans only care that the resolution is
1114/// unproven and match `Err(_)`.
1115#[derive(Debug, Clone, PartialEq, Eq)]
1116pub enum CppTemplateResolutionError {
1117    /// A template alias expansion revisited `alias`.
1118    AliasCycle { alias: CodeUnit },
1119    /// The explicit arguments do not bind to the declared template parameters.
1120    ArgumentBinding,
1121    /// Bound arguments do not substitute into the alias target's arguments.
1122    Substitution,
1123    /// No visible primary template declaration could be selected and
1124    /// reconciled for the specialization family.
1125    PrimarySelection,
1126    /// More than one applicable specialization remains and none is strictly
1127    /// more specialized than every other candidate.
1128    AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1129}
1130
1131/// The ambiguity candidates, deduplicated to one representative per visible
1132/// symbol so a diagnostic lists each contender once.
1133fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1134    let mut distinct: Vec<CodeUnit> = Vec::new();
1135    for unit in units {
1136        if !distinct
1137            .iter()
1138            .any(|existing| same_visible_symbol(existing, unit))
1139        {
1140            distinct.push(unit.clone());
1141        }
1142    }
1143    distinct
1144}
1145
1146impl<'a> VisibilityIndex<'a> {
1147    pub fn cpp(&self) -> &'a dyn CppSource {
1148        self.cpp
1149    }
1150
1151    /// A [`VisibilityIndex`] over a caller-supplied visible-declaration map,
1152    /// bypassing the include-closure walk [`Self::build`] performs.
1153    ///
1154    /// The resolver's own unit tests drive the type-resolution paths against a
1155    /// hand-written visibility table; they live in `brokk-bifrost-analysis`
1156    /// because they need a real `CppAnalyzer`, so the struct literal they used
1157    /// to write inline is here instead of thirty-three public fields.
1158    #[cfg(any(test, feature = "test-support"))]
1159    pub fn from_visible_files_for_test(
1160        cpp: &'a dyn CppSource,
1161        visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1162    ) -> Self {
1163        let visible_source_files_by_root = visible_by_file
1164            .iter()
1165            .map(|(file, visible)| {
1166                (
1167                    file.clone(),
1168                    visible
1169                        .iter()
1170                        .map(|unit| unit.source().clone())
1171                        .chain(std::iter::once(file.clone()))
1172                        .collect(),
1173                )
1174            })
1175            .collect();
1176        let mut global_field_internal_linkage = HashMap::default();
1177        Self {
1178            cpp,
1179            visible_by_identifier: build_visible_identifier_index(
1180                &CppGraphSource::from_source(cpp),
1181                &visible_by_file,
1182                &visible_source_files_by_root,
1183                &mut global_field_internal_linkage,
1184            ),
1185            global_field_internal_linkage,
1186            visible_by_file,
1187            visible_source_files_by_root,
1188            alias_cells: Mutex::new(HashMap::default()),
1189            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1190            visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1191            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1192            project_using_index: OnceLock::new(),
1193            callable_reference_specs: Mutex::new(HashMap::default()),
1194            include_activation_cells: Mutex::new(HashMap::default()),
1195            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1196            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1197            conditional_include_projection_state_count: AtomicUsize::new(0),
1198            include_activation_build_count: AtomicUsize::new(0),
1199            using_donor_activation_count: AtomicUsize::new(0),
1200            using_namespace_lookup_count: AtomicUsize::new(0),
1201            using_name_candidate_inspection_count: AtomicUsize::new(0),
1202            callable_reference_spec_build_count: AtomicUsize::new(0),
1203            alias_source_parse_counts: Mutex::new(HashMap::default()),
1204            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1205            visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1206            field_type_facts: Mutex::new(HashMap::default()),
1207            structured_alias_targets: Mutex::new(HashMap::default()),
1208            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1209            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1210            precise_parent_cache: Mutex::new(HashMap::default()),
1211            macro_event_cells: Mutex::new(HashMap::default()),
1212            macro_include_protection_cells: Mutex::new(HashMap::default()),
1213            macro_environment_cursors: Mutex::new(HashMap::default()),
1214            macro_replacements: Mutex::new(HashMap::default()),
1215            macro_local_binding_templates: Mutex::new(HashMap::default()),
1216            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1217            macro_replacement_parse_count: AtomicUsize::new(0),
1218            macro_event_application_count: AtomicUsize::new(0),
1219            macro_environment_copy_count: AtomicUsize::new(0),
1220            cpp_template_metadata: HashMap::default(),
1221            cpp_template_families: HashMap::default(),
1222            qualified_candidate_inspections: AtomicUsize::new(0),
1223            target_preserving_type_resolution_count: AtomicUsize::new(0),
1224        }
1225    }
1226
1227    /// The index's own C++ source, in the dispatching-analyzer shape.
1228    ///
1229    /// Four resolution paths reach the workspace through the C++ analyzer they
1230    /// already hold rather than through the analyzer the query was issued
1231    /// against; before the move they passed `&CppAnalyzer` straight into a
1232    /// `&dyn IAnalyzer` parameter. See [`CppGraphSource::from_source`].
1233    fn cpp_source(&self) -> CppGraphSource<'a> {
1234        CppGraphSource::from_source(self.cpp)
1235    }
1236
1237    pub fn build(
1238        cpp: &'a dyn CppSource,
1239        analyzer: &CppGraphSource<'_>,
1240        roots: &HashSet<ProjectFile>,
1241    ) -> Self {
1242        Self::build_with_cancellation(cpp, analyzer, roots, None)
1243    }
1244
1245    pub fn build_with_cancellation(
1246        cpp: &'a dyn CppSource,
1247        analyzer: &CppGraphSource<'_>,
1248        roots: &HashSet<ProjectFile>,
1249        cancellation: Option<&CancellationToken>,
1250    ) -> Self {
1251        let include_targets = cpp.include_target_index();
1252        let VisibilityData {
1253            mut visible_by_file,
1254            visible_source_files_by_root,
1255        } = build_visibility_data(
1256            roots,
1257            cancellation,
1258            |file| {
1259                let imports = analyzer.import_statements(file);
1260                cpp_include_paths(&imports)
1261                    .into_iter()
1262                    .flat_map(|include| {
1263                        resolve_include_targets_with_index(file, &include, include_targets)
1264                    })
1265                    .collect()
1266            },
1267            |file| analyzer.declarations(file),
1268        );
1269        extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
1270        let mut global_field_internal_linkage = HashMap::default();
1271        let visible_by_identifier = build_visible_identifier_index(
1272            analyzer,
1273            &visible_by_file,
1274            &visible_source_files_by_root,
1275            &mut global_field_internal_linkage,
1276        );
1277        let mut cpp_template_metadata = HashMap::default();
1278        for unit in visible_by_file
1279            .values()
1280            .flatten()
1281            .filter(|unit| unit.is_class())
1282        {
1283            if cpp_template_metadata.contains_key(unit) {
1284                continue;
1285            }
1286            if let Some(metadata) = cpp.template_metadata(unit) {
1287                cpp_template_metadata.insert(unit.clone(), metadata);
1288            }
1289        }
1290        let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1291        for (unit, metadata) in &cpp_template_metadata {
1292            cpp_template_families
1293                .entry(metadata.primary_fq_name.clone())
1294                .or_default()
1295                .push(unit.clone());
1296        }
1297        // `cpp_template_metadata` is hash-keyed on `CodeUnit`, so the push
1298        // order above is a function of those hashes. Two mirrored headers can
1299        // declare one specialization; `select_template_specialization` treats
1300        // them as interchangeable and returns the family's first entry, so an
1301        // unsorted family made the reported declaration depend on the
1302        // workspace's absolute path and on unrelated files (#1836). Order the
1303        // family exactly as `build_visible_identifier_index` orders its
1304        // per-identifier candidate lists.
1305        for family in cpp_template_families.values_mut() {
1306            sort_lookup_units(family);
1307        }
1308        Self {
1309            cpp,
1310            visible_by_file,
1311            visible_by_identifier,
1312            global_field_internal_linkage,
1313            visible_source_files_by_root,
1314            alias_cells: Mutex::new(HashMap::default()),
1315            visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1316            visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1317            ordinary_type_import_cells: Mutex::new(HashMap::default()),
1318            project_using_index: OnceLock::new(),
1319            callable_reference_specs: Mutex::new(HashMap::default()),
1320            include_activation_cells: Mutex::new(HashMap::default()),
1321            conditional_include_projection_cells: Mutex::new(HashMap::default()),
1322            #[cfg(any(test, feature = "test-support"))]
1323            conditional_include_projection_index_build_count: AtomicUsize::new(0),
1324            #[cfg(any(test, feature = "test-support"))]
1325            conditional_include_projection_state_count: AtomicUsize::new(0),
1326            #[cfg(any(test, feature = "test-support"))]
1327            include_activation_build_count: AtomicUsize::new(0),
1328            #[cfg(any(test, feature = "test-support"))]
1329            using_donor_activation_count: AtomicUsize::new(0),
1330            #[cfg(any(test, feature = "test-support"))]
1331            using_namespace_lookup_count: AtomicUsize::new(0),
1332            #[cfg(any(test, feature = "test-support"))]
1333            using_name_candidate_inspection_count: AtomicUsize::new(0),
1334            #[cfg(any(test, feature = "test-support"))]
1335            callable_reference_spec_build_count: AtomicUsize::new(0),
1336            #[cfg(any(test, feature = "test-support"))]
1337            alias_source_parse_counts: Mutex::new(HashMap::default()),
1338            #[cfg(any(test, feature = "test-support"))]
1339            visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1340            #[cfg(any(test, feature = "test-support"))]
1341            visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1342            field_type_facts: Mutex::new(HashMap::default()),
1343            structured_alias_targets: Mutex::new(HashMap::default()),
1344            indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1345            indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1346            precise_parent_cache: Mutex::new(HashMap::default()),
1347            macro_event_cells: Mutex::new(HashMap::default()),
1348            macro_include_protection_cells: Mutex::new(HashMap::default()),
1349            macro_environment_cursors: Mutex::new(HashMap::default()),
1350            macro_replacements: Mutex::new(HashMap::default()),
1351            macro_local_binding_templates: Mutex::new(HashMap::default()),
1352            callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1353            #[cfg(any(test, feature = "test-support"))]
1354            macro_replacement_parse_count: AtomicUsize::new(0),
1355            #[cfg(any(test, feature = "test-support"))]
1356            macro_event_application_count: AtomicUsize::new(0),
1357            #[cfg(any(test, feature = "test-support"))]
1358            macro_environment_copy_count: AtomicUsize::new(0),
1359            cpp_template_metadata,
1360            cpp_template_families,
1361            #[cfg(any(test, feature = "test-support"))]
1362            qualified_candidate_inspections: AtomicUsize::new(0),
1363            #[cfg(any(test, feature = "test-support"))]
1364            target_preserving_type_resolution_count: AtomicUsize::new(0),
1365        }
1366    }
1367
1368    pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1369        if file == target.source() {
1370            return true;
1371        }
1372        if self.global_field_has_internal_linkage(target) {
1373            return self
1374                .visible_source_files_by_root
1375                .get(file)
1376                .is_some_and(|sources| sources.contains(target.source()));
1377        }
1378        self.visible_by_file
1379            .get(file)
1380            .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
1381    }
1382
1383    fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
1384        self.global_field_internal_linkage
1385            .get(unit)
1386            .copied()
1387            .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
1388    }
1389
1390    pub fn call_arity_evidence(
1391        &self,
1392        file: &ProjectFile,
1393        call: Node<'_>,
1394        source: &str,
1395    ) -> CallArityEvidence {
1396        let Some(arguments) = call
1397            .child_by_field_name("arguments")
1398            .or_else(|| call.child_by_field_name("parameters"))
1399            .or_else(|| call.child_by_field_name("value"))
1400            .or_else(|| first_named_child_of_kind(call, "argument_list"))
1401            .or_else(|| first_named_child_of_kind(call, "initializer_list"))
1402        else {
1403            return CallArityEvidence::Exact(0);
1404        };
1405        let recovered_c_keyword_arguments =
1406            recovered_c_keyword_argument_count(file, call, arguments, source);
1407        let arguments = argument_children(arguments).collect::<Vec<_>>();
1408        if arguments
1409            .iter()
1410            .all(|argument| !argument_shape_may_change_arity(*argument))
1411        {
1412            return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
1413        }
1414        let environment = self.macro_environment(file, call.start_byte());
1415        let mut stack = Vec::new();
1416        let mut total = recovered_c_keyword_arguments;
1417        for argument in arguments {
1418            if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
1419                return CallArityEvidence::Unknown;
1420            }
1421            let CallArityEvidence::Exact(spread) =
1422                self.argument_arity_evidence(argument, source, &environment, &mut stack)
1423            else {
1424                return CallArityEvidence::Unknown;
1425            };
1426            total += spread;
1427        }
1428        CallArityEvidence::Exact(total)
1429    }
1430
1431    fn argument_arity_evidence(
1432        &self,
1433        argument: Node<'_>,
1434        source: &str,
1435        environment: &MacroEnvironment,
1436        stack: &mut Vec<(ProjectFile, usize)>,
1437    ) -> CallArityEvidence {
1438        let (name, invocation_arguments, function_like) = match argument.kind() {
1439            "identifier" => (node_text(argument, source), None, false),
1440            "call_expression" => {
1441                let Some(function) = argument.child_by_field_name("function") else {
1442                    return CallArityEvidence::Exact(1);
1443                };
1444                if function.kind() != "identifier" {
1445                    return CallArityEvidence::Exact(1);
1446                }
1447                let Some(arguments) = argument.child_by_field_name("arguments") else {
1448                    return CallArityEvidence::Exact(1);
1449                };
1450                (node_text(function, source), Some(arguments), true)
1451            }
1452            _ => return CallArityEvidence::Exact(1),
1453        };
1454        let Some(binding) = environment.binding(name) else {
1455            return if environment.unknown_names {
1456                CallArityEvidence::Unknown
1457            } else {
1458                CallArityEvidence::Exact(1)
1459            };
1460        };
1461        if !binding.is_exact() {
1462            return CallArityEvidence::Unknown;
1463        }
1464        match (&binding.definition, invocation_arguments, function_like) {
1465            (MacroDefinition::Object { replacement }, None, false) => self
1466                .replacement_arity_evidence(
1467                    replacement,
1468                    &[],
1469                    &[],
1470                    source,
1471                    environment,
1472                    stack,
1473                    binding,
1474                ),
1475            (
1476                MacroDefinition::Function {
1477                    parameters,
1478                    replacement,
1479                },
1480                Some(arguments),
1481                true,
1482            ) => {
1483                let actuals = argument_children(arguments).collect::<Vec<_>>();
1484                if actuals.len() != parameters.len() {
1485                    CallArityEvidence::Unknown
1486                } else {
1487                    self.replacement_arity_evidence(
1488                        replacement,
1489                        parameters,
1490                        &actuals,
1491                        source,
1492                        environment,
1493                        stack,
1494                        binding,
1495                    )
1496                }
1497            }
1498            (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
1499            _ => CallArityEvidence::Unknown,
1500        }
1501    }
1502
1503    #[allow(clippy::too_many_arguments)]
1504    fn replacement_arity_evidence(
1505        &self,
1506        replacement: &str,
1507        parameters: &[String],
1508        actuals: &[Node<'_>],
1509        actual_source: &str,
1510        environment: &MacroEnvironment,
1511        stack: &mut Vec<(ProjectFile, usize)>,
1512        binding: &MacroBinding,
1513    ) -> CallArityEvidence {
1514        let identity = (binding.source.clone(), binding.declaration_byte);
1515        if stack.contains(&identity) || replacement.trim().is_empty() {
1516            return CallArityEvidence::Unknown;
1517        }
1518        stack.push(identity);
1519        let parsed = self.parsed_macro_replacement(binding, replacement);
1520        let evidence = (|| {
1521            let ParsedMacroReplacement::Parsed {
1522                source: sentinel,
1523                tree,
1524            } = parsed.as_ref()
1525            else {
1526                return None;
1527            };
1528            let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
1529            let arguments = call.child_by_field_name("arguments")?;
1530            let mut total = 0usize;
1531            for argument in argument_children(arguments) {
1532                if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
1533                    return None;
1534                }
1535                if argument.kind() == "identifier"
1536                    && let Some(parameter_index) = parameters
1537                        .iter()
1538                        .position(|parameter| parameter == node_text(argument, sentinel))
1539                {
1540                    if !macro_expansion_shape_is_safe(
1541                        actuals[parameter_index],
1542                        actual_source,
1543                        &[],
1544                        environment,
1545                    ) {
1546                        return None;
1547                    }
1548                    let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
1549                        actuals[parameter_index],
1550                        actual_source,
1551                        environment,
1552                        stack,
1553                    ) else {
1554                        return None;
1555                    };
1556                    total += spread;
1557                    continue;
1558                }
1559                let CallArityEvidence::Exact(spread) =
1560                    self.argument_arity_evidence(argument, sentinel, environment, stack)
1561                else {
1562                    return None;
1563                };
1564                total += spread;
1565            }
1566            Some(CallArityEvidence::Exact(total))
1567        })()
1568        .unwrap_or(CallArityEvidence::Unknown);
1569        stack.pop();
1570        evidence
1571    }
1572
1573    fn parsed_macro_replacement(
1574        &self,
1575        binding: &MacroBinding,
1576        replacement: &str,
1577    ) -> Arc<ParsedMacroReplacement> {
1578        let key = (binding.source.clone(), binding.declaration_byte);
1579        let mut cache = self
1580            .macro_replacements
1581            .lock()
1582            .expect("C++ macro replacement cache poisoned");
1583        if let Some(parsed) = cache.get(&key) {
1584            return Arc::clone(parsed);
1585        }
1586        #[cfg(any(test, feature = "test-support"))]
1587        self.macro_replacement_parse_count
1588            .fetch_add(1, Ordering::Relaxed);
1589        let source =
1590            format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
1591        let mut parser = Parser::new();
1592        let parsed = parser
1593            .set_language(&tree_sitter_cpp::LANGUAGE.into())
1594            .ok()
1595            .and_then(|()| parser.parse(&source, None))
1596            .filter(|tree| !tree.root_node().has_error())
1597            .map_or(ParsedMacroReplacement::Unsupported, |tree| {
1598                ParsedMacroReplacement::Parsed { source, tree }
1599            });
1600        let parsed = Arc::new(parsed);
1601        cache.insert(key, Arc::clone(&parsed));
1602        parsed
1603    }
1604
1605    /// Recover a typed local declared by an active C function-like macro.
1606    ///
1607    /// This is intentionally narrower than macro expansion. The replacement
1608    /// must parse as one declaration, and the invocation must bind every
1609    /// formal parameter to one structured argument. That is sufficient for
1610    /// declaration macros such as `THIS(StorageAzure)`. An unavailable include
1611    /// can make the binding provisional without erasing its last known
1612    /// definition; an explicit conflicting definition still replaces it with
1613    /// Unsupported. Malformed and statement-producing macros also fail closed.
1614    pub fn function_macro_local_binding<'tree>(
1615        &self,
1616        file: &ProjectFile,
1617        statement: Node<'tree>,
1618        source: &str,
1619    ) -> Option<MacroLocalBinding<'tree>> {
1620        if !is_c_source_file(file) {
1621            return None;
1622        }
1623        let call = match statement.kind() {
1624            "call_expression" => statement,
1625            "expression_statement" if statement.named_child_count() == 1 => {
1626                statement.named_child(0)?
1627            }
1628            _ => return None,
1629        };
1630        if call.kind() != "call_expression" {
1631            return None;
1632        }
1633        let function = call.child_by_field_name("function")?;
1634        if function.kind() != "identifier" {
1635            return None;
1636        }
1637        let arguments = call.child_by_field_name("arguments")?;
1638        let actuals = argument_children(arguments).collect::<Vec<_>>();
1639        let environment = self.macro_environment(file, call.start_byte());
1640        let function_name = node_text(function, source);
1641        let binding = environment.binding(function_name)?;
1642        let MacroDefinition::Function {
1643            parameters,
1644            replacement,
1645        } = &binding.definition
1646        else {
1647            return None;
1648        };
1649        if actuals.len() != parameters.len() {
1650            return None;
1651        }
1652        let template = self.macro_local_binding_template(binding, parameters, replacement)?;
1653        let (type_name, type_node) = match &template.declared_type {
1654            MacroLocalBindingTypeTemplate::Parameter(index) => {
1655                let actual = *actuals.get(*index)?;
1656                if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
1657                    return None;
1658                }
1659                (node_text(actual, source).trim().to_string(), Some(actual))
1660            }
1661            MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
1662        };
1663        if type_name.is_empty() {
1664            return None;
1665        }
1666        Some(MacroLocalBinding {
1667            name: template.name.clone(),
1668            type_name,
1669            type_node,
1670            pointer_depth: template.pointer_depth,
1671        })
1672    }
1673
1674    fn macro_local_binding_template(
1675        &self,
1676        binding: &MacroBinding,
1677        parameters: &[String],
1678        replacement: &str,
1679    ) -> Option<Arc<MacroLocalBindingTemplate>> {
1680        let key = (binding.source.clone(), binding.declaration_byte);
1681        let mut cache = self
1682            .macro_local_binding_templates
1683            .lock()
1684            .expect("C++ macro local-binding cache poisoned");
1685        if let Some(template) = cache.get(&key) {
1686            return template.clone();
1687        }
1688        let sentinel = format!("void __bifrost_macro_local() {{ {replacement}; }}");
1689        let template = (|| {
1690            let mut parser = Parser::new();
1691            parser
1692                .set_language(&tree_sitter_cpp::LANGUAGE.into())
1693                .ok()?;
1694            let tree = parser.parse(&sentinel, None)?;
1695            if tree.root_node().has_error() {
1696                return None;
1697            }
1698            let function = first_descendant_of_kind(tree.root_node(), "function_definition")?;
1699            let body = function.child_by_field_name("body")?;
1700            if body.named_child_count() != 1 {
1701                return None;
1702            }
1703            let declaration = body.named_child(0)?;
1704            if declaration.kind() != "declaration" {
1705                return None;
1706            }
1707            let type_node = declaration
1708                .child_by_field_name("type")
1709                .or_else(|| first_type_child(declaration))?;
1710            let declarator = declaration.child_by_field_name("declarator").or_else(|| {
1711                let mut cursor = declaration.walk();
1712                declaration.named_children(&mut cursor).find_map(|child| {
1713                    if child.kind() == "init_declarator" {
1714                        child.child_by_field_name("declarator")
1715                    } else {
1716                        is_declarator_node(child).then_some(child)
1717                    }
1718                })
1719            })?;
1720            let name = extract_variable_name(declarator, &sentinel)?;
1721            let pointer_depth =
1722                declared_name_indirection(declaration, type_node, &name, &sentinel)?;
1723            let type_text = node_text(type_node, &sentinel).trim();
1724            let declared_type = parameters
1725                .iter()
1726                .position(|parameter| parameter == type_text)
1727                .map(MacroLocalBindingTypeTemplate::Parameter)
1728                .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
1729            Some(Arc::new(MacroLocalBindingTemplate {
1730                name,
1731                declared_type,
1732                pointer_depth,
1733            }))
1734        })();
1735        cache.insert(key, template.clone());
1736        template
1737    }
1738
1739    fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
1740        let Some(value) = node.child_by_field_name("value") else {
1741            return MacroDefinition::Unsupported;
1742        };
1743        let replacement = node_text(value, source).to_string();
1744        if node.kind() == "preproc_def" {
1745            return MacroDefinition::Object { replacement };
1746        }
1747        let Some(parameters) = node.child_by_field_name("parameters") else {
1748            return MacroDefinition::Unsupported;
1749        };
1750        if (0..parameters.child_count()).any(|index| {
1751            parameters
1752                .child(index)
1753                .is_some_and(|child| child.kind() == "...")
1754        }) {
1755            return MacroDefinition::Unsupported;
1756        }
1757        let parameters = (0..parameters.named_child_count())
1758            .filter_map(|index| parameters.named_child(index))
1759            .map(|parameter| node_text(parameter, source).to_string())
1760            .collect();
1761        MacroDefinition::Function {
1762            parameters,
1763            replacement,
1764        }
1765    }
1766
1767    pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
1768        self.macro_event_cells
1769            .lock()
1770            .expect("C++ macro event cache poisoned")
1771            .entry(file.clone())
1772            .or_default()
1773            .clone()
1774    }
1775
1776    pub fn macro_environment_cursor_cell(&self, file: &ProjectFile) -> MacroEnvironmentCursorCell {
1777        let key = (file.clone(), std::thread::current().id());
1778        self.macro_environment_cursors
1779            .lock()
1780            .expect("C++ macro environment cursor cache poisoned")
1781            .entry(key)
1782            .or_default()
1783            .clone()
1784    }
1785
1786    pub fn macro_environment(
1787        &self,
1788        file: &ProjectFile,
1789        before_byte: usize,
1790    ) -> Arc<MacroEnvironment> {
1791        let cell = self.macro_event_cell(file);
1792        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
1793        let frontier = events.partition_point(|event| event.byte() < before_byte);
1794        let cursor_cell = self.macro_environment_cursor_cell(file);
1795        let mut cursor = cursor_cell
1796            .lock()
1797            .expect("C++ macro environment cursor poisoned");
1798        if frontier < cursor.frontier {
1799            *cursor = MacroEnvironmentCursor::default();
1800        }
1801        if frontier > cursor.frontier {
1802            #[cfg(any(test, feature = "test-support"))]
1803            if Arc::strong_count(&cursor.environment) > 1 {
1804                self.macro_environment_copy_count
1805                    .fetch_add(1, Ordering::Relaxed);
1806            }
1807            let start = cursor.frontier;
1808            let environment = Arc::make_mut(&mut cursor.environment);
1809            let mut include_stack = HashSet::from_iter([file.clone()]);
1810            for event in &events[start..frontier] {
1811                self.apply_macro_event(file, event, environment, &mut include_stack);
1812            }
1813            cursor.frontier = frontier;
1814        }
1815        Arc::clone(&cursor.environment)
1816    }
1817
1818    /// Whether `name` is bound as a macro at `before_byte` in `file`,
1819    /// including a binding this environment cannot pin to one replacement
1820    /// (a conditional `#define`, or a function-like macro).
1821    ///
1822    /// [`Self::object_macro_replacement_at`] collapses every such binding to
1823    /// `None`, which is indistinguishable from "not a macro at all". A caller
1824    /// that must not read a macro token as an ordinary type name needs the two
1825    /// apart: an unexpandable macro is an unknown, a plain identifier is not.
1826    pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
1827        self.macro_environment(file, before_byte)
1828            .binding(name)
1829            .is_some()
1830    }
1831
1832    pub fn macro_name_may_be_bound_at(
1833        &self,
1834        file: &ProjectFile,
1835        name: &str,
1836        before_byte: usize,
1837    ) -> bool {
1838        self.macro_environment(file, before_byte).may_bind(name)
1839    }
1840
1841    /// Whether the active macro binding at this reference is the requested
1842    /// indexed definition. Name equality alone is not enough because two
1843    /// headers can define the same macro for different translation units.
1844    pub fn macro_binding_matches_target_at(
1845        &self,
1846        analyzer: &CppGraphSource<'_>,
1847        file: &ProjectFile,
1848        name: &str,
1849        before_byte: usize,
1850        target: &CodeUnit,
1851    ) -> bool {
1852        let environment = self.macro_environment(file, before_byte);
1853        let Some(binding) = environment.binding(name) else {
1854            return false;
1855        };
1856        // A normal header guard makes the replacement text conditional, but
1857        // it does not erase the definition site's source and byte identity.
1858        // Keep that identity even when expansion details are not exact.
1859        if binding.source != *target.source() {
1860            return false;
1861        }
1862        let Some(prepared) = self.cpp.prepared_syntax(target.source()) else {
1863            return false;
1864        };
1865        analyzer.ranges(target).iter().any(|range| {
1866            let Some(mut node) = node_for_exact_range(prepared.tree().root_node(), range) else {
1867                return false;
1868            };
1869            while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
1870                let Some(parent) = node.parent() else {
1871                    return false;
1872                };
1873                node = parent;
1874            }
1875            node.start_byte() == binding.declaration_byte
1876        })
1877    }
1878
1879    /// Resolve an ordinary expression-position macro token at its exact byte.
1880    ///
1881    /// Calls and preprocessor-condition tokens have separate resolution
1882    /// surfaces. Declaration names, macro parameters, and labels are not
1883    /// references. Keeping that role policy here makes forward and both
1884    /// inverse graph builders consume the same activation verdict (#2093).
1885    pub fn resolve_ordinary_macro_reference(
1886        &self,
1887        analyzer: &CppGraphSource<'_>,
1888        file: &ProjectFile,
1889        node: Node<'_>,
1890        source: &str,
1891    ) -> OrdinaryMacroReferenceResolution {
1892        if !is_ordinary_macro_reference_node(node) {
1893            return OrdinaryMacroReferenceResolution::Missing;
1894        }
1895        let name = node_text(node, source);
1896        if name.is_empty() {
1897            return OrdinaryMacroReferenceResolution::Missing;
1898        }
1899        let visible = self
1900            .visible_identifier_candidates(file, name)
1901            .filter(|candidate| candidate.is_macro())
1902            .cloned()
1903            .collect::<Vec<_>>();
1904        let mut exact = Vec::new();
1905        for candidate in &visible {
1906            if self.macro_binding_matches_target_at(
1907                analyzer,
1908                file,
1909                name,
1910                node.start_byte(),
1911                candidate,
1912            ) && !exact
1913                .iter()
1914                .any(|existing| same_visible_symbol(existing, candidate))
1915            {
1916                exact.push(candidate.clone());
1917            }
1918        }
1919        match exact.len() {
1920            1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
1921            2.. => OrdinaryMacroReferenceResolution::Ambiguous,
1922            0 if !visible.is_empty()
1923                && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
1924            {
1925                OrdinaryMacroReferenceResolution::Ambiguous
1926            }
1927            0 => OrdinaryMacroReferenceResolution::Missing,
1928        }
1929    }
1930
1931    /// Collect reference-capable C tokens beneath tree-sitter recovery nodes.
1932    ///
1933    /// The ordinary census deliberately skips every `ERROR` subtree. This
1934    /// separate, precision-only frontier admits only roles that retain enough
1935    /// structure for the C usage graph to interpret independently (#2089).
1936    /// Macro evidence comes from this visibility index at the exact byte; no
1937    /// source-text parsing or terminal-name fallback is used.
1938    pub fn recovered_c_reference_ranges(
1939        &self,
1940        file: &ProjectFile,
1941        root: Node<'_>,
1942        source: &str,
1943        limit: usize,
1944    ) -> RecoveredCReferenceRanges {
1945        if !is_c_source_file(file) {
1946            return RecoveredCReferenceRanges::Complete(Vec::new());
1947        }
1948        let mut ranges = Vec::new();
1949        let mut seen = HashSet::default();
1950        let mut stack = vec![(root, root.is_error())];
1951        while let Some((node, inside_error)) = stack.pop() {
1952            let inside_error = inside_error || node.is_error();
1953            if inside_error
1954                && recovered_c_reference_node(self, file, node, source)
1955                && seen.insert((node.start_byte(), node.end_byte()))
1956            {
1957                if ranges.len() == limit {
1958                    return RecoveredCReferenceRanges::LimitExceeded;
1959                }
1960                ranges.push(Range {
1961                    start_byte: node.start_byte(),
1962                    end_byte: node.end_byte(),
1963                    start_line: node.start_position().row,
1964                    end_line: node.end_position().row,
1965                });
1966            }
1967            let mut cursor = node.walk();
1968            for child in node.named_children(&mut cursor) {
1969                stack.push((child, inside_error));
1970            }
1971        }
1972        ranges.sort_unstable();
1973        RecoveredCReferenceRanges::Complete(ranges)
1974    }
1975
1976    /// Whether this target is an indexed macro visible from this file.
1977    ///
1978    /// An unresolved conditional can make more than one same-name macro a
1979    /// possible active binding. Each possible target can keep the site as an
1980    /// unproven hit. A macro in an unrelated translation unit stays excluded.
1981    pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1982        self.visible_identifier_candidates(file, target.identifier())
1983            .filter(|candidate| candidate.is_macro())
1984            .any(|candidate| {
1985                candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
1986            })
1987    }
1988
1989    pub fn object_macro_replacement_at(
1990        &self,
1991        file: &ProjectFile,
1992        name: &str,
1993        before_byte: usize,
1994    ) -> Option<String> {
1995        let environment = self.macro_environment(file, before_byte);
1996        let binding = environment.binding(name)?;
1997        if !binding.exact {
1998            return None;
1999        }
2000        match &binding.definition {
2001            MacroDefinition::Object { replacement } => Some(replacement.clone()),
2002            MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
2003        }
2004    }
2005
2006    fn apply_macro_events(
2007        &self,
2008        file: &ProjectFile,
2009        before_byte: Option<usize>,
2010        environment: &mut MacroEnvironment,
2011        include_stack: &mut HashSet<ProjectFile>,
2012    ) {
2013        if !include_stack.insert(file.clone()) {
2014            return;
2015        }
2016        if self.cpp.prepared_syntax(file).is_none() {
2017            environment.mark_unknown_names(file, before_byte.unwrap_or_default());
2018            include_stack.remove(file);
2019            return;
2020        }
2021        match self.macro_include_protection(file) {
2022            MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
2023                Some(binding) if binding.is_exact() => {
2024                    include_stack.remove(file);
2025                    return;
2026                }
2027                Some(_) | None if environment.unknown_names => {
2028                    let mut ambiguous_seen = HashSet::default();
2029                    self.mark_macro_events_ambiguous(
2030                        file,
2031                        environment,
2032                        &mut ambiguous_seen,
2033                        file,
2034                        before_byte.unwrap_or_default(),
2035                    );
2036                    include_stack.remove(file);
2037                    return;
2038                }
2039                Some(_) => {
2040                    let mut ambiguous_seen = HashSet::default();
2041                    self.mark_macro_events_ambiguous(
2042                        file,
2043                        environment,
2044                        &mut ambiguous_seen,
2045                        file,
2046                        before_byte.unwrap_or_default(),
2047                    );
2048                    include_stack.remove(file);
2049                    return;
2050                }
2051                None => {}
2052            },
2053            MacroIncludeProtection::PragmaOnce => {
2054                if !environment.applied_pragma_once_files.insert(file.clone()) {
2055                    include_stack.remove(file);
2056                    return;
2057                }
2058                if environment.maybe_applied_pragma_once_files.remove(file) {
2059                    // A prior conditional include may already have consumed the pragma-once
2060                    // header. This unconditional include guarantees it is consumed now, but
2061                    // cannot prove whether its events occur before or after intervening local
2062                    // macro changes, so preserve the union as ambiguous.
2063                    let mut ambiguous_seen = HashSet::default();
2064                    environment.applied_pragma_once_files.remove(file);
2065                    self.mark_macro_events_ambiguous(
2066                        file,
2067                        environment,
2068                        &mut ambiguous_seen,
2069                        file,
2070                        before_byte.unwrap_or_default(),
2071                    );
2072                    environment.maybe_applied_pragma_once_files.remove(file);
2073                    environment.applied_pragma_once_files.insert(file.clone());
2074                    include_stack.remove(file);
2075                    return;
2076                }
2077            }
2078            MacroIncludeProtection::None => {}
2079        }
2080        let cell = self.macro_event_cell(file);
2081        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2082        for event in events {
2083            if before_byte.is_some_and(|limit| event.byte() >= limit) {
2084                break;
2085            }
2086            self.apply_macro_event(file, event, environment, include_stack);
2087        }
2088        include_stack.remove(file);
2089    }
2090
2091    fn apply_macro_event(
2092        &self,
2093        file: &ProjectFile,
2094        event: &MacroEvent,
2095        environment: &mut MacroEnvironment,
2096        include_stack: &mut HashSet<ProjectFile>,
2097    ) {
2098        #[cfg(any(test, feature = "test-support"))]
2099        self.macro_event_application_count
2100            .fetch_add(1, Ordering::Relaxed);
2101        match event {
2102            MacroEvent::Define {
2103                name,
2104                binding,
2105                conditional,
2106                byte,
2107            } => {
2108                if *conditional {
2109                    Self::merge_conditional_macro_definition(
2110                        environment,
2111                        name,
2112                        binding,
2113                        file,
2114                        *byte,
2115                    );
2116                } else {
2117                    environment.insert(name.clone(), binding.clone());
2118                }
2119            }
2120            MacroEvent::Undef {
2121                name,
2122                conditional,
2123                byte,
2124            } => {
2125                if *conditional {
2126                    if environment.binding(name).is_some() {
2127                        environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
2128                    }
2129                } else {
2130                    environment.remove(name);
2131                }
2132            }
2133            MacroEvent::Include {
2134                targets,
2135                conditional,
2136                byte,
2137            } => {
2138                if targets.is_empty() {
2139                    environment.mark_unknown_names(file, *byte);
2140                    return;
2141                }
2142                if *conditional || targets.len() > 1 {
2143                    let mut ambiguous_seen = HashSet::default();
2144                    for target in targets {
2145                        self.mark_macro_events_ambiguous(
2146                            target,
2147                            environment,
2148                            &mut ambiguous_seen,
2149                            file,
2150                            *byte,
2151                        );
2152                    }
2153                } else if let Some(target) = targets.first() {
2154                    self.apply_macro_events(target, None, environment, include_stack);
2155                }
2156            }
2157            MacroEvent::Invalidate { byte } => {
2158                for binding in environment.bindings.values_mut() {
2159                    *binding = MacroBinding::uncertain_from(binding, file, *byte);
2160                }
2161            }
2162        }
2163    }
2164
2165    fn mark_macro_events_ambiguous(
2166        &self,
2167        file: &ProjectFile,
2168        environment: &mut MacroEnvironment,
2169        include_stack: &mut HashSet<ProjectFile>,
2170        conditional_file: &ProjectFile,
2171        conditional_byte: usize,
2172    ) {
2173        if !include_stack.insert(file.clone()) {
2174            return;
2175        }
2176        if self.cpp.prepared_syntax(file).is_none() {
2177            environment.mark_unknown_names(conditional_file, conditional_byte);
2178            return;
2179        }
2180        match self.macro_include_protection(file) {
2181            MacroIncludeProtection::MacroGuard(guard) => {
2182                if environment
2183                    .binding(&guard)
2184                    .is_some_and(MacroBinding::is_exact)
2185                {
2186                    return;
2187                }
2188            }
2189            MacroIncludeProtection::PragmaOnce => {
2190                if environment.applied_pragma_once_files.contains(file) {
2191                    return;
2192                }
2193                environment
2194                    .maybe_applied_pragma_once_files
2195                    .insert(file.clone());
2196            }
2197            MacroIncludeProtection::None => {}
2198        }
2199        let cell = self.macro_event_cell(file);
2200        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2201        for event in events {
2202            #[cfg(any(test, feature = "test-support"))]
2203            self.macro_event_application_count
2204                .fetch_add(1, Ordering::Relaxed);
2205            match event {
2206                MacroEvent::Define { name, binding, .. } => {
2207                    Self::merge_conditional_macro_definition(
2208                        environment,
2209                        name,
2210                        binding,
2211                        conditional_file,
2212                        conditional_byte,
2213                    );
2214                }
2215                MacroEvent::Undef { name, .. } => {
2216                    if environment.binding(name).is_some() {
2217                        environment.insert(
2218                            name.clone(),
2219                            MacroBinding::ambiguous(conditional_file, conditional_byte),
2220                        );
2221                    }
2222                }
2223                MacroEvent::Include { targets, .. } => {
2224                    if targets.is_empty() {
2225                        environment.mark_unknown_names(conditional_file, conditional_byte);
2226                        continue;
2227                    }
2228                    for target in targets {
2229                        self.mark_macro_events_ambiguous(
2230                            target,
2231                            environment,
2232                            include_stack,
2233                            conditional_file,
2234                            conditional_byte,
2235                        );
2236                    }
2237                }
2238                MacroEvent::Invalidate { .. } => {
2239                    for binding in environment.bindings.values_mut() {
2240                        *binding = MacroBinding::uncertain_from(
2241                            binding,
2242                            conditional_file,
2243                            conditional_byte,
2244                        );
2245                    }
2246                }
2247            }
2248        }
2249    }
2250
2251    fn merge_conditional_macro_definition(
2252        environment: &mut MacroEnvironment,
2253        name: &str,
2254        possible_binding: &MacroBinding,
2255        conditional_file: &ProjectFile,
2256        conditional_byte: usize,
2257    ) {
2258        // A conditional include can revisit an already-active guarded header.
2259        // If the possible branch defines the exact same macro, both outcomes
2260        // leave the binding unchanged; degrading it to Unknown would discard
2261        // proof because of an unrelated unresolved macro name (#2092).
2262        if environment.binding(name).is_some_and(|current| {
2263            current.definition != MacroDefinition::Unsupported
2264                && current.definition == possible_binding.definition
2265        }) {
2266            return;
2267        }
2268        environment.insert(
2269            name.to_string(),
2270            MacroBinding::ambiguous(conditional_file, conditional_byte),
2271        );
2272    }
2273
2274    pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
2275        let cell = self
2276            .macro_include_protection_cells
2277            .lock()
2278            .expect("C++ include protection cache poisoned")
2279            .entry(file.clone())
2280            .or_default()
2281            .clone();
2282        cell.get_or_init(|| {
2283            self.cpp
2284                .prepared_syntax(file)
2285                .map_or(MacroIncludeProtection::None, |prepared| {
2286                    top_level_macro_include_protection(
2287                        prepared.tree().root_node(),
2288                        prepared.source(),
2289                    )
2290                })
2291        })
2292        .clone()
2293    }
2294
2295    fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
2296        let Some(prepared) = self.cpp.prepared_syntax(file) else {
2297            return Vec::new();
2298        };
2299        let source = prepared.source();
2300        let mut events = Vec::new();
2301        let mut stack = vec![prepared.tree().root_node()];
2302        while let Some(node) = stack.pop() {
2303            let conditional = has_preprocessor_conditional_ancestor(node, source);
2304            match node.kind() {
2305                "preproc_def" | "preproc_function_def" => {
2306                    let Some(name) = node.child_by_field_name("name") else {
2307                        continue;
2308                    };
2309                    let name = node_text(name, source).to_string();
2310                    events.push(MacroEvent::Define {
2311                        name,
2312                        binding: MacroBinding {
2313                            source: file.clone(),
2314                            declaration_byte: node.start_byte(),
2315                            definition: Self::decode_macro_definition(node, source),
2316                            exact: true,
2317                        },
2318                        byte: node.start_byte(),
2319                        conditional,
2320                    });
2321                    continue;
2322                }
2323                "preproc_include" => {
2324                    let Some(path) = node.child_by_field_name("path") else {
2325                        events.push(MacroEvent::Include {
2326                            targets: Vec::new(),
2327                            byte: node.start_byte(),
2328                            conditional,
2329                        });
2330                        continue;
2331                    };
2332                    let targets =
2333                        structured_include_path(path, source).map_or_else(Vec::new, |path| {
2334                            resolve_include_targets_with_index(
2335                                file,
2336                                path,
2337                                self.cpp.include_target_index(),
2338                            )
2339                        });
2340                    // An unresolved angle-bracket include crosses into an external system
2341                    // boundary that is absent from the source index. It must not poison all
2342                    // later local macro evidence. Quoted/project-local and computed includes,
2343                    // by contrast, may hide indexed macro state and therefore fail closed.
2344                    if targets.is_empty() && path.kind() == "system_lib_string" {
2345                        continue;
2346                    }
2347                    events.push(MacroEvent::Include {
2348                        targets,
2349                        byte: node.start_byte(),
2350                        conditional,
2351                    });
2352                    continue;
2353                }
2354                "preproc_call" => {
2355                    let Some(directive) = node.child_by_field_name("directive") else {
2356                        continue;
2357                    };
2358                    if node_text(directive, source) != "#undef" {
2359                        continue;
2360                    }
2361                    let name = node
2362                        .child_by_field_name("argument")
2363                        .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
2364                    if let Some(name) = name {
2365                        events.push(MacroEvent::Undef {
2366                            name,
2367                            byte: node.start_byte(),
2368                            conditional,
2369                        });
2370                    } else {
2371                        events.push(MacroEvent::Invalidate {
2372                            byte: node.start_byte(),
2373                        });
2374                    }
2375                    continue;
2376                }
2377                _ => {}
2378            }
2379            for index in (0..node.named_child_count()).rev() {
2380                if let Some(child) = node.named_child(index) {
2381                    stack.push(child);
2382                }
2383            }
2384        }
2385        events.sort_by_key(MacroEvent::byte);
2386        events
2387    }
2388
2389    pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
2390        self.ordinary_type_import_cells
2391            .lock()
2392            .expect("C++ ordinary type import cache poisoned")
2393            .entry(file.clone())
2394            .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
2395            .clone()
2396    }
2397
2398    pub fn project_using_index(
2399        &self,
2400        build: impl FnOnce() -> ProjectUsingIndex,
2401    ) -> &ProjectUsingIndex {
2402        self.project_using_index.get_or_init(build)
2403    }
2404
2405    pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
2406        let mut files = self
2407            .visible_source_files_by_root
2408            .values()
2409            .flatten()
2410            .cloned()
2411            .collect::<HashSet<_>>()
2412            .into_iter()
2413            .collect::<Vec<_>>();
2414        files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
2415        files
2416    }
2417
2418    pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
2419        self.visible_source_files_by_root
2420            .get(root)
2421            .is_some_and(|files| files.contains(source))
2422    }
2423
2424    fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
2425        let cached = self
2426            .visible_parser_alias_name_sets
2427            .read()
2428            .expect("visible parser alias-name cache poisoned")
2429            .get(file)
2430            .cloned();
2431        let cell = if let Some(cached) = cached {
2432            cached
2433        } else {
2434            let mut cells = self
2435                .visible_parser_alias_name_sets
2436                .write()
2437                .expect("visible parser alias-name cache poisoned");
2438            Arc::clone(
2439                cells
2440                    .entry(file.clone())
2441                    .or_insert_with(|| Arc::new(OnceLock::new())),
2442            )
2443        };
2444        cell.get_or_init(|| {
2445            #[cfg(any(test, feature = "test-support"))]
2446            self.visible_parser_alias_name_set_build_count
2447                .fetch_add(1, Ordering::Relaxed);
2448            let mut names = HashSet::default();
2449            let visible_files = self
2450                .visible_source_files_by_root
2451                .get(file)
2452                .cloned()
2453                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2454            for visible_file in visible_files {
2455                let aliases = {
2456                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2457                    Arc::clone(
2458                        cells
2459                            .entry(visible_file.clone())
2460                            .or_insert_with(|| Arc::new(OnceLock::new())),
2461                    )
2462                };
2463                for alias in aliases
2464                    .get_or_init(|| {
2465                        #[cfg(any(test, feature = "test-support"))]
2466                        {
2467                            *self
2468                                .alias_source_parse_counts
2469                                .lock()
2470                                .expect("alias source parse count lock")
2471                                .entry(visible_file.clone())
2472                                .or_default() += 1;
2473                        }
2474                        aliases_from_prepared_source(self.cpp, &visible_file).into_boxed_slice()
2475                    })
2476                    .iter()
2477                {
2478                    names.insert(alias.name.clone());
2479                }
2480            }
2481            names
2482        })
2483        .contains(name)
2484    }
2485
2486    fn visible_parser_alias_names_for_target(
2487        &self,
2488        file: &ProjectFile,
2489        target: &CodeUnit,
2490    ) -> HashSet<String> {
2491        let cell = {
2492            let mut cells = self
2493                .visible_parser_alias_target_names
2494                .lock()
2495                .expect("visible parser alias-target cache poisoned");
2496            Arc::clone(
2497                cells
2498                    .entry(file.clone())
2499                    .or_insert_with(|| Arc::new(OnceLock::new())),
2500            )
2501        };
2502        let target_name = cpp_name_for(target);
2503        cell.get_or_init(|| {
2504            #[cfg(any(test, feature = "test-support"))]
2505            self.visible_parser_alias_target_names_build_count
2506                .fetch_add(1, Ordering::Relaxed);
2507            let visible_files = self
2508                .visible_source_files_by_root
2509                .get(file)
2510                .cloned()
2511                .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2512            let mut names_by_target = HashMap::<String, HashSet<String>>::default();
2513            for visible_file in visible_files {
2514                let aliases = {
2515                    let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2516                    Arc::clone(
2517                        cells
2518                            .entry(visible_file.clone())
2519                            .or_insert_with(|| Arc::new(OnceLock::new())),
2520                    )
2521                };
2522                for alias in aliases
2523                    .get_or_init(|| {
2524                        #[cfg(any(test, feature = "test-support"))]
2525                        {
2526                            *self
2527                                .alias_source_parse_counts
2528                                .lock()
2529                                .expect("alias source parse count lock")
2530                                .entry(visible_file.clone())
2531                                .or_default() += 1;
2532                        }
2533                        aliases_from_prepared_source(self.cpp, &visible_file).into_boxed_slice()
2534                    })
2535                    .iter()
2536                {
2537                    for target_name in parser_alias_target_names(alias) {
2538                        names_by_target
2539                            .entry(target_name)
2540                            .or_default()
2541                            .insert(alias.name.clone());
2542                    }
2543                }
2544            }
2545            names_by_target
2546        })
2547        .get(&target_name)
2548        .cloned()
2549        .unwrap_or_default()
2550    }
2551
2552    fn callable_arities_for_target(
2553        &self,
2554        analyzer: &CppGraphSource<'_>,
2555        cpp: &dyn CppSource,
2556        file: &ProjectFile,
2557        prepared: &PreparedSyntaxTree,
2558        spec: &TargetSpec,
2559    ) -> Vec<ActivatedCallableArity> {
2560        let Some(signature) = spec.target.signature() else {
2561            return Vec::new();
2562        };
2563        let Some(candidates) = self
2564            .visible_by_identifier
2565            .get(file)
2566            .and_then(|by_name| by_name.get(&spec.member_name))
2567        else {
2568            return Vec::new();
2569        };
2570        let differing_candidates = candidates
2571            .iter()
2572            .filter(|candidate| {
2573                candidate.is_function()
2574                    && candidate.fq_name() == spec.target.fq_name()
2575                    && candidate.signature() == Some(signature)
2576            })
2577            .filter_map(|candidate| {
2578                analyzer
2579                    .signature_metadata(candidate)
2580                    .into_iter()
2581                    .find_map(|metadata| metadata.callable_arity())
2582                    .filter(|arity| Some(*arity) != spec.callable_arity)
2583                    .map(|arity| (candidate, arity))
2584            })
2585            .collect::<Vec<_>>();
2586        if differing_candidates.is_empty() {
2587            return Vec::new();
2588        }
2589        let mut arities = Vec::with_capacity(differing_candidates.len());
2590        // The activation ranges here describe the whole file rather than one
2591        // reference, so there is no reference guard environment to consult.
2592        let reference = CallableReferenceContext {
2593            file,
2594            position: None,
2595        };
2596        for (candidate, candidate_arity) in differing_candidates {
2597            let declaration_activation = if candidate.source() == file {
2598                callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
2599            } else {
2600                cpp.prepared_syntax(candidate.source()).and_then(|syntax| {
2601                    callable_declaration_activation_in_file(
2602                        analyzer,
2603                        syntax.as_ref(),
2604                        candidate,
2605                        &reference,
2606                    )
2607                })
2608            };
2609            let Some(declaration_activation) = declaration_activation else {
2610                continue;
2611            };
2612            let activation_byte = if candidate.source() == file {
2613                Some(declaration_activation)
2614            } else {
2615                self.include_activation_for_source(cpp, file, prepared, candidate.source())
2616            };
2617            if let Some(activation_byte) = activation_byte {
2618                arities.push(ActivatedCallableArity {
2619                    activation_byte,
2620                    arity: candidate_arity,
2621                });
2622            }
2623        }
2624        arities
2625    }
2626
2627    fn callable_parameter_macro_arity(
2628        &self,
2629        target: &CodeUnit,
2630        signature: Option<&str>,
2631    ) -> Option<CallableArity> {
2632        let parameter_types = cpp_signature_param_types(signature?)?;
2633        let [macro_name] = parameter_types.as_slice() else {
2634            return None;
2635        };
2636        if macro_name.is_empty()
2637            || !macro_name
2638                .chars()
2639                .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
2640        {
2641            return None;
2642        }
2643        let cache_key = (target.source().clone(), macro_name.clone());
2644        if let Some(cached) = self
2645            .callable_parameter_macro_arities
2646            .lock()
2647            .expect("C++ callable parameter-macro arity cache poisoned")
2648            .get(&cache_key)
2649            .copied()
2650        {
2651            return cached;
2652        }
2653        let mut visible_files = HashSet::default();
2654        collect_include_closure(
2655            &self.cpp_source(),
2656            self.cpp.include_target_index(),
2657            target.source(),
2658            &mut visible_files,
2659            None,
2660        );
2661        let mut arities = Vec::new();
2662        for visible_file in visible_files {
2663            let cell = self.macro_event_cell(&visible_file);
2664            for event in
2665                cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
2666            {
2667                let MacroEvent::Define { name, binding, .. } = event else {
2668                    continue;
2669                };
2670                if name != macro_name {
2671                    continue;
2672                }
2673                let MacroDefinition::Object { replacement } = &binding.definition else {
2674                    continue;
2675                };
2676                let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
2677                    continue;
2678                };
2679                if !arities.contains(&arity) {
2680                    arities.push(arity);
2681                }
2682            }
2683        }
2684        let resolved = (|| {
2685            let required = arities
2686                .iter()
2687                .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
2688                .min()?;
2689            let total = arities.iter().map(|arity| arity.total()).max()?;
2690            let repeated = arities
2691                .iter()
2692                .any(|arity| arity.accepts(arity.total().saturating_add(1)));
2693            // Preprocessor conditions can leave more than one object-like parameter
2694            // bundle active in the target header's include closure. Preserve their
2695            // conservative callable envelope instead of choosing whichever definition
2696            // happened to be visited first.
2697            Some(CallableArity::new(required, total, repeated))
2698        })();
2699        self.callable_parameter_macro_arities
2700            .lock()
2701            .expect("C++ callable parameter-macro arity cache poisoned")
2702            .insert(cache_key, resolved);
2703        resolved
2704    }
2705
2706    pub fn include_activation_for_source(
2707        &self,
2708        cpp: &dyn CppSource,
2709        file: &ProjectFile,
2710        prepared: &PreparedSyntaxTree,
2711        donor_source: &ProjectFile,
2712    ) -> Option<usize> {
2713        let key = (file.clone(), donor_source.clone());
2714        if let Some(cached) = self
2715            .include_activation_cells
2716            .lock()
2717            .expect("C++ include activation cache poisoned")
2718            .get(&key)
2719            .copied()
2720        {
2721            return cached;
2722        }
2723        #[cfg(any(test, feature = "test-support"))]
2724        self.include_activation_build_count
2725            .fetch_add(1, Ordering::Relaxed);
2726        let activation = find_include_activation(cpp, file, prepared, donor_source);
2727        let mut cells = self
2728            .include_activation_cells
2729            .lock()
2730            .expect("C++ include activation cache poisoned");
2731        *cells.entry(key).or_insert(activation)
2732    }
2733
2734    pub fn conditional_include_projections_for_source(
2735        &self,
2736        file: &ProjectFile,
2737        prepared: &PreparedSyntaxTree,
2738        donor_source: &ProjectFile,
2739    ) -> Arc<[ConditionalIncludeProjection]> {
2740        static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
2741        let cell = self
2742            .conditional_include_projection_cells
2743            .lock()
2744            .expect("C++ conditional include projection cache poisoned")
2745            .entry(file.clone())
2746            .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
2747            .clone();
2748        let index = cell.get_or_build_pool_independent(|| {
2749            #[cfg(any(test, feature = "test-support"))]
2750            self.conditional_include_projection_index_build_count
2751                .fetch_add(1, Ordering::Relaxed);
2752            find_conditional_include_projection_index(self.cpp, file, prepared, &|| {
2753                #[cfg(any(test, feature = "test-support"))]
2754                self.conditional_include_projection_state_count
2755                    .fetch_add(1, Ordering::Relaxed);
2756            })
2757        });
2758        index
2759            .get(donor_source)
2760            .cloned()
2761            .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
2762    }
2763
2764    #[cfg(any(test, feature = "test-support"))]
2765    pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
2766        (
2767            self.conditional_include_projection_index_build_count
2768                .load(Ordering::Relaxed),
2769            self.conditional_include_projection_state_count
2770                .load(Ordering::Relaxed),
2771        )
2772    }
2773
2774    #[cfg(any(test, feature = "test-support"))]
2775    pub fn include_activation_build_count_for_test(&self) -> usize {
2776        self.include_activation_build_count.load(Ordering::Relaxed)
2777    }
2778
2779    #[cfg(any(test, feature = "test-support"))]
2780    pub fn note_using_donor_activation_for_test(&self) {
2781        self.using_donor_activation_count
2782            .fetch_add(1, Ordering::Relaxed);
2783    }
2784
2785    #[cfg(not(any(test, feature = "test-support")))]
2786    pub fn note_using_donor_activation_for_test(&self) {}
2787
2788    #[cfg(any(test, feature = "test-support"))]
2789    pub fn note_using_namespace_lookup_for_test(&self) {
2790        self.using_namespace_lookup_count
2791            .fetch_add(1, Ordering::Relaxed);
2792    }
2793
2794    #[cfg(not(any(test, feature = "test-support")))]
2795    pub fn note_using_namespace_lookup_for_test(&self) {}
2796
2797    #[cfg(any(test, feature = "test-support"))]
2798    pub fn note_using_name_candidate_inspection_for_test(&self) {
2799        self.using_name_candidate_inspection_count
2800            .fetch_add(1, Ordering::Relaxed);
2801    }
2802
2803    #[cfg(not(any(test, feature = "test-support")))]
2804    pub fn note_using_name_candidate_inspection_for_test(&self) {}
2805
2806    #[cfg(any(test, feature = "test-support"))]
2807    pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
2808        (
2809            self.using_donor_activation_count.load(Ordering::Relaxed),
2810            self.using_namespace_lookup_count.load(Ordering::Relaxed),
2811            self.callable_reference_spec_build_count
2812                .load(Ordering::Relaxed),
2813            self.using_name_candidate_inspection_count
2814                .load(Ordering::Relaxed),
2815        )
2816    }
2817
2818    pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2819        file == target.source()
2820            || self
2821                .visible_by_file
2822                .get(file)
2823                .is_some_and(|visible| visible.contains(target))
2824    }
2825
2826    pub fn declaration_visible_at(
2827        &self,
2828        analyzer: &CppGraphSource<'_>,
2829        file: &ProjectFile,
2830        declaration: &CodeUnit,
2831        reference_byte: usize,
2832    ) -> bool {
2833        let reference_guards = OnceCell::new();
2834        self.visible_identifier_candidates(file, declaration.identifier())
2835            .filter(|candidate| {
2836                same_logical_symbol(candidate, declaration)
2837                    || flattened_macro_namespace_declaration_matches(
2838                        analyzer,
2839                        self.cpp,
2840                        file,
2841                        candidate,
2842                        declaration,
2843                        reference_byte,
2844                    )
2845            })
2846            .any(|candidate| {
2847                self.physical_declaration_visible_at(
2848                    analyzer,
2849                    file,
2850                    candidate,
2851                    reference_byte,
2852                    &reference_guards,
2853                )
2854            })
2855    }
2856
2857    pub fn callable_arity_at_reference(
2858        &self,
2859        analyzer: &CppGraphSource<'_>,
2860        file: &ProjectFile,
2861        candidate: &CodeUnit,
2862        reference_byte: usize,
2863    ) -> Option<CallableArity> {
2864        let key = (file.clone(), logical_symbol_key(candidate));
2865        let cell = self
2866            .callable_reference_specs
2867            .lock()
2868            .expect("C++ callable reference-spec cache poisoned")
2869            .entry(key)
2870            .or_default()
2871            .clone();
2872        let spec = cell.get_or_init(|| {
2873            let prepared = self.cpp.prepared_syntax(file)?;
2874            let spec = TargetSpec::from_target(analyzer, candidate)?;
2875            let spec = spec
2876                .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
2877                .into_owned();
2878            #[cfg(any(test, feature = "test-support"))]
2879            self.callable_reference_spec_build_count
2880                .fetch_add(1, Ordering::Relaxed);
2881            Some(spec)
2882        });
2883        spec.as_ref()?.callable_arity_at(reference_byte)
2884    }
2885
2886    fn physical_declaration_visible_at(
2887        &self,
2888        analyzer: &CppGraphSource<'_>,
2889        file: &ProjectFile,
2890        declaration: &CodeUnit,
2891        reference_byte: usize,
2892        reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
2893    ) -> bool {
2894        let Some(prepared) = self.cpp.prepared_syntax(file) else {
2895            return false;
2896        };
2897        let reference = CallableReferenceContext {
2898            file,
2899            position: Some(CallableReferencePosition {
2900                prepared: prepared.as_ref(),
2901                byte: reference_byte,
2902                guards: reference_guards,
2903            }),
2904        };
2905        if declaration.source() == file {
2906            return callable_declaration_activation_in_file(
2907                analyzer,
2908                prepared.as_ref(),
2909                declaration,
2910                &reference,
2911            )
2912            .or_else(|| {
2913                self.exhaustive_guard_family_activation(
2914                    analyzer,
2915                    prepared.as_ref(),
2916                    declaration,
2917                    &reference,
2918                )
2919            })
2920            .is_some_and(|activation| activation < reference_byte);
2921        }
2922        let Some(donor_syntax) = self.cpp.prepared_syntax(declaration.source()) else {
2923            return false;
2924        };
2925        if callable_declaration_activation_in_file(
2926            analyzer,
2927            donor_syntax.as_ref(),
2928            declaration,
2929            &reference,
2930        )
2931        .or_else(|| {
2932            self.exhaustive_guard_family_activation(
2933                analyzer,
2934                donor_syntax.as_ref(),
2935                declaration,
2936                &reference,
2937            )
2938        })
2939        .is_none()
2940        {
2941            return false;
2942        }
2943        declaration_guard_requirements(analyzer, self.cpp, declaration)
2944            .into_iter()
2945            .any(|(_, declaration_guards)| {
2946                self.foreign_declaration_reachable_at_reference(
2947                    file,
2948                    prepared.as_ref(),
2949                    declaration.source(),
2950                    &declaration_guards,
2951                    reference.guards(),
2952                    reference_byte,
2953                )
2954            })
2955    }
2956
2957    pub fn external_type_candidate_visible_at(
2958        &self,
2959        file: &ProjectFile,
2960        candidate: &CodeUnit,
2961        reference_byte: usize,
2962    ) -> bool {
2963        if candidate.source() == file {
2964            return true;
2965        }
2966        let Some(prepared) = self.cpp.prepared_syntax(file) else {
2967            return false;
2968        };
2969        self.visible_identifier_candidates(file, candidate.identifier())
2970            .filter(|peer| same_logical_symbol(candidate, peer))
2971            .any(|peer| {
2972                peer.source() == file
2973                    || self
2974                        .include_activation_for_source(
2975                            self.cpp,
2976                            file,
2977                            prepared.as_ref(),
2978                            peer.source(),
2979                        )
2980                        .is_some_and(|activation| activation <= reference_byte)
2981            })
2982    }
2983
2984    pub fn external_type_declaration_visible_at(
2985        &self,
2986        file: &ProjectFile,
2987        candidate: &CodeUnit,
2988        reference_byte: usize,
2989    ) -> bool {
2990        if candidate.source() == file {
2991            return true;
2992        }
2993        let Some(prepared) = self.cpp.prepared_syntax(file) else {
2994            return false;
2995        };
2996        self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
2997            .is_some_and(|activation| activation <= reference_byte)
2998    }
2999
3000    /// Decide whether a declaration that lives in another file reaches a
3001    /// reference in `file`.
3002    ///
3003    /// An external header selects its declaration branch before the reference
3004    /// file is parsed. Require compatible reference guards, but do not test
3005    /// the header's guard expression for stability in the reference file: a
3006    /// `.c` translation unit can never satisfy the `#ifdef __cplusplus` that
3007    /// wraps every declaration of a portable C header, and demanding it would
3008    /// hide the whole header. Guards that the reference file imposes on its
3009    /// own `#include` still have to hold, and still have to be stable.
3010    fn foreign_declaration_reachable_at_reference(
3011        &self,
3012        file: &ProjectFile,
3013        prepared: &PreparedSyntaxTree,
3014        declaration_source: &ProjectFile,
3015        declaration_guards: &HashSet<PreprocessorGuard>,
3016        reference_guards: Option<&HashSet<PreprocessorGuard>>,
3017        reference_byte: usize,
3018    ) -> bool {
3019        if !guards_compatible_at_reference(declaration_guards, reference_guards) {
3020            return false;
3021        }
3022        if self
3023            .include_activation_for_source(self.cpp, file, prepared, declaration_source)
3024            .is_some_and(|activation| activation <= reference_byte)
3025        {
3026            return true;
3027        }
3028        self.conditional_include_projections_for_source(file, prepared, declaration_source)
3029            .iter()
3030            .any(|projection| {
3031                projection.activation_byte <= reference_byte
3032                    && guard_requirements_hold_at_reference(
3033                        &projection.required_guards,
3034                        reference_guards,
3035                    )
3036                    && self.preprocessor_guards_stable_between(
3037                        file,
3038                        projection.activation_byte,
3039                        reference_byte,
3040                        &projection.required_guards,
3041                    )
3042            })
3043    }
3044
3045    pub fn external_type_candidate_visible_in_context(
3046        &self,
3047        analyzer: &CppGraphSource<'_>,
3048        file: &ProjectFile,
3049        candidate: &CodeUnit,
3050        reference: Node<'_>,
3051    ) -> bool {
3052        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3053            return false;
3054        };
3055        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3056
3057        let directly_visible = self
3058            .visible_identifier_candidates(file, candidate.identifier())
3059            .filter(|peer| same_logical_symbol(candidate, peer))
3060            .any(|peer| {
3061                declaration_guard_requirements(analyzer, self.cpp, peer)
3062                    .into_iter()
3063                    .any(|(declaration_byte, declaration_guards)| {
3064                        if peer.source() == file {
3065                            return declaration_byte < reference.start_byte()
3066                                && guard_requirements_hold_at_reference(
3067                                    &declaration_guards,
3068                                    reference_guards.as_ref(),
3069                                )
3070                                && self.preprocessor_guards_stable_between(
3071                                    file,
3072                                    declaration_byte,
3073                                    reference.start_byte(),
3074                                    &declaration_guards,
3075                                );
3076                        }
3077                        self.foreign_declaration_reachable_at_reference(
3078                            file,
3079                            prepared.as_ref(),
3080                            peer.source(),
3081                            &declaration_guards,
3082                            reference_guards.as_ref(),
3083                            reference.start_byte(),
3084                        )
3085                    })
3086            });
3087        let complementary = self
3088            .visible_identifier_candidates(file, candidate.identifier())
3089            .filter(|peer| {
3090                peer.kind() == candidate.kind()
3091                    && peer.fq_name() == candidate.fq_name()
3092                    && peer.source() == candidate.source()
3093            })
3094            .collect::<Vec<_>>();
3095        // A completed #if/#else family declares the shared source-level name
3096        // before this reference. A later macro mutation cannot revoke that
3097        // declaration. The family gate below rejects declarations split across
3098        // separate conditional blocks, where mutation can change coverage.
3099        let candidate_branch_compatible = reference_guards.as_ref().is_some_and(|active| {
3100            declaration_guard_requirements(analyzer, self.cpp, candidate)
3101                .iter()
3102                .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
3103        });
3104        let complementary_visible = candidate_branch_compatible
3105            && self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate)
3106            && if candidate.source() == file {
3107                declaration_guard_requirements(analyzer, self.cpp, candidate)
3108                    .iter()
3109                    .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
3110            } else {
3111                self.include_activation_for_source(
3112                    self.cpp,
3113                    file,
3114                    prepared.as_ref(),
3115                    candidate.source(),
3116                )
3117                .is_some_and(|activation| activation <= reference.start_byte())
3118            };
3119        directly_visible || complementary_visible
3120    }
3121
3122    pub fn is_exhaustive_same_fqn_type_declaration_family(
3123        &self,
3124        analyzer: &CppGraphSource<'_>,
3125        file: &ProjectFile,
3126        candidate: &CodeUnit,
3127    ) -> bool {
3128        let candidates = self
3129            .visible_identifier_candidates(file, candidate.identifier())
3130            .filter(|peer| {
3131                peer.kind() == candidate.kind()
3132                    && peer.fq_name() == candidate.fq_name()
3133                    && peer.source() == candidate.source()
3134            })
3135            .collect::<Vec<_>>();
3136        self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
3137    }
3138
3139    /// Prove a nested type alias used as a dependent member-pointer owner when
3140    /// its owning class has mutually-exclusive declarations.  A common C++11
3141    /// compatibility shape provides the owning class in one preprocessor
3142    /// branch and aliases it to a standard-library type in the other branch;
3143    /// the nested fallback alias is therefore not itself active in every
3144    /// branch even though the qualified owner API is.
3145    ///
3146    /// This is deliberately narrower than ordinary type visibility.  The
3147    /// caller has already recovered a member-pointer owner path from the CST;
3148    /// this helper additionally requires the target's structured parent to
3149    /// match that path, physical source visibility, and exact preprocessor
3150    /// guard agreement with the parent declaration.  Only then may the
3151    /// parent's direct/complementary same-FQN visibility stand in for the
3152    /// nested terminal's active-branch check.
3153    pub fn dependent_member_pointer_alias_visible_in_context(
3154        &self,
3155        analyzer: &CppGraphSource<'_>,
3156        file: &ProjectFile,
3157        candidate: &CodeUnit,
3158        owner_components: &[String],
3159        reference: Node<'_>,
3160    ) -> bool {
3161        if !analyzer
3162            .type_alias_provider()
3163            .is_some_and(|provider| provider.is_type_alias(candidate))
3164        {
3165            return false;
3166        }
3167        let Some((terminal, owner_prefix)) = owner_components.split_last() else {
3168            return false;
3169        };
3170        if terminal != candidate.identifier()
3171            || canonical_cpp_scope_components(candidate) != owner_components
3172        {
3173            return false;
3174        }
3175        let Some(expected_parent_fq_name) =
3176            brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
3177        else {
3178            return false;
3179        };
3180        let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
3181            return false;
3182        };
3183        if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
3184            || parent_anchor.source() != candidate.source()
3185            || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
3186        {
3187            return false;
3188        }
3189
3190        // The ordinary path already handles unguarded aliases (and preserves
3191        // same-file declaration ordering).  This fallback is only for a
3192        // physically visible declaration whose guard is the owning branch's
3193        // guard, so reject a same-file declaration that appears after the
3194        // reference before considering guard compatibility.
3195        if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
3196            || candidate.source() == file
3197                && !analyzer
3198                    .ranges(candidate)
3199                    .iter()
3200                    .any(|range| range.start_byte < reference.start_byte())
3201        {
3202            return false;
3203        }
3204
3205        let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
3206        if candidate_guards.is_empty() {
3207            return false;
3208        }
3209        let same_guard_sets =
3210            |left: &[(usize, HashSet<PreprocessorGuard>)],
3211             right: &[(usize, HashSet<PreprocessorGuard>)]| {
3212                left.iter().all(|(_, left_guards)| {
3213                    right
3214                        .iter()
3215                        .any(|(_, right_guards)| left_guards == right_guards)
3216                })
3217            };
3218        let parent_candidates = self
3219            .visible_identifier_candidates(file, parent_anchor.identifier())
3220            .filter(|peer| {
3221                peer.kind() == parent_anchor.kind()
3222                    && peer.fq_name() == expected_parent_fq_name.as_str()
3223                    && peer.source() == parent_anchor.source()
3224                    && canonical_cpp_scope_components(peer) == owner_prefix
3225            })
3226            .filter_map(|peer| {
3227                let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
3228                (candidate_guards.len() == parent_guards.len()
3229                    && same_guard_sets(&candidate_guards, &parent_guards)
3230                    && same_guard_sets(&parent_guards, &candidate_guards))
3231                .then(|| (peer.clone(), parent_guards))
3232            })
3233            .collect::<Vec<_>>();
3234        let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
3235            return false;
3236        };
3237
3238        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3239            return false;
3240        };
3241        let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
3242        else {
3243            return false;
3244        };
3245        // An external header selects its declaration branch before the
3246        // reference file is parsed. Require compatible reference guards, but
3247        // do not test the header's guard expression for stability in the
3248        // reference file. Same-file aliases still require that stability.
3249        if !candidate_guards.iter().any(|(_, target_guards)| {
3250            guards_compatible_at_reference(target_guards, Some(&reference_guards))
3251                && (candidate.source() != file
3252                    || self.preprocessor_guards_stable_between(
3253                        file,
3254                        0,
3255                        reference.start_byte(),
3256                        target_guards,
3257                    ))
3258        }) {
3259            return false;
3260        }
3261
3262        self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
3263    }
3264
3265    /// Check a type candidate's preprocessor/import context without imposing
3266    /// ordinary declaration-before-reference ordering for same-file peers.
3267    ///
3268    /// C++ class scope makes member names visible throughout the complete
3269    /// class, including a trailing return type that appears before the member
3270    /// alias declaration in source order. Callers must first prove that the
3271    /// reference is inside the candidate's indexed class owner; this helper
3272    /// only relaxes the byte-order predicate while retaining guard and include
3273    /// activation checks.
3274    pub fn external_type_candidate_guard_compatible_in_context(
3275        &self,
3276        analyzer: &CppGraphSource<'_>,
3277        file: &ProjectFile,
3278        candidate: &CodeUnit,
3279        reference: Node<'_>,
3280    ) -> bool {
3281        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3282            return false;
3283        };
3284        let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3285
3286        self.visible_identifier_candidates(file, candidate.identifier())
3287            .filter(|peer| same_logical_symbol(candidate, peer))
3288            .any(|peer| {
3289                declaration_guard_requirements(analyzer, self.cpp, peer)
3290                    .into_iter()
3291                    .any(|(declaration_byte, declaration_guards)| {
3292                        if peer.source() == file {
3293                            let (start, end) = if declaration_byte <= reference.start_byte() {
3294                                (declaration_byte, reference.start_byte())
3295                            } else {
3296                                (reference.start_byte(), declaration_byte)
3297                            };
3298                            return guard_requirements_hold_at_reference(
3299                                &declaration_guards,
3300                                reference_guards.as_ref(),
3301                            ) && self.preprocessor_guards_stable_between(
3302                                file,
3303                                start,
3304                                end,
3305                                &declaration_guards,
3306                            );
3307                        }
3308                        self.foreign_declaration_reachable_at_reference(
3309                            file,
3310                            prepared.as_ref(),
3311                            peer.source(),
3312                            &declaration_guards,
3313                            reference_guards.as_ref(),
3314                            reference.start_byte(),
3315                        )
3316                    })
3317            })
3318    }
3319
3320    pub fn type_candidate_may_be_visible_before_reference(
3321        &self,
3322        analyzer: &CppGraphSource<'_>,
3323        file: &ProjectFile,
3324        candidate: &CodeUnit,
3325        reference_byte: usize,
3326    ) -> bool {
3327        let Some(prepared) = self.cpp.prepared_syntax(file) else {
3328            return false;
3329        };
3330        let root = prepared.tree().root_node();
3331        let end_byte = reference_byte
3332            .saturating_add(1)
3333            .min(prepared.source().len());
3334        let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
3335            return false;
3336        };
3337        self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
3338    }
3339
3340    pub fn preprocessor_guards_stable_between(
3341        &self,
3342        file: &ProjectFile,
3343        start_byte: usize,
3344        end_byte: usize,
3345        guards: &HashSet<PreprocessorGuard>,
3346    ) -> bool {
3347        if guards.is_empty() || start_byte >= end_byte {
3348            return true;
3349        }
3350        let cell = self.macro_event_cell(file);
3351        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3352        let mut visited = HashSet::from_iter([file.clone()]);
3353        !events.iter().any(|event| {
3354            event.byte() >= start_byte
3355                && event.byte() < end_byte
3356                && self.macro_event_may_mutate_guards(event, guards, &mut visited)
3357        })
3358    }
3359
3360    fn macro_event_may_mutate_guards(
3361        &self,
3362        event: &MacroEvent,
3363        guards: &HashSet<PreprocessorGuard>,
3364        visited: &mut HashSet<ProjectFile>,
3365    ) -> bool {
3366        match event {
3367            MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
3368                guards.iter().any(|guard| guard.may_depend_on_macro(name))
3369            }
3370            MacroEvent::Include { targets, .. } => {
3371                targets.is_empty()
3372                    || targets
3373                        .iter()
3374                        .any(|target| self.source_may_mutate_guards(target, guards, visited))
3375            }
3376            MacroEvent::Invalidate { .. } => true,
3377        }
3378    }
3379
3380    fn source_may_mutate_guards(
3381        &self,
3382        file: &ProjectFile,
3383        guards: &HashSet<PreprocessorGuard>,
3384        visited: &mut HashSet<ProjectFile>,
3385    ) -> bool {
3386        if !visited.insert(file.clone()) {
3387            return false;
3388        }
3389        let cell = self.macro_event_cell(file);
3390        let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3391        events
3392            .iter()
3393            .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
3394    }
3395
3396    pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
3397        let normalized = normalize_reference_name(raw_name)?;
3398        self.type_candidates(file, &normalized)
3399            .into_iter()
3400            .next()
3401            .cloned()
3402    }
3403
3404    pub fn resolve_type_node_result(
3405        &self,
3406        file: &ProjectFile,
3407        node: Node<'_>,
3408        source: &str,
3409    ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
3410        let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
3411            return Ok(None);
3412        };
3413        let Some(arguments) = cpp_template_reference_arguments(node, source) else {
3414            return Ok(Some(primary));
3415        };
3416        self.resolve_template_arguments(file, primary, &arguments)
3417            .map(Some)
3418    }
3419
3420    pub fn resolve_type_node_primary(
3421        &self,
3422        file: &ProjectFile,
3423        node: Node<'_>,
3424        source: &str,
3425    ) -> Option<CodeUnit> {
3426        let components = cpp_type_name_components(node, source)?;
3427        self.resolve_type(file, &components.join("::"))
3428    }
3429
3430    pub fn resolve_template_arguments(
3431        &self,
3432        file: &ProjectFile,
3433        primary: CodeUnit,
3434        arguments: &[CppTemplateExpression],
3435    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3436        self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
3437    }
3438
3439    fn resolve_template_arguments_inner(
3440        &self,
3441        file: &ProjectFile,
3442        primary: CodeUnit,
3443        arguments: &[CppTemplateExpression],
3444        seen_aliases: &mut HashSet<CodeUnit>,
3445    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3446        if let Some(metadata) = self.cpp_template_metadata.get(&primary)
3447            && let Some(alias_target) = &metadata.alias_target
3448        {
3449            if !seen_aliases.insert(primary.clone()) {
3450                return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
3451            }
3452            let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
3453                .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3454            let target_name = alias_target.components.join("::");
3455            let target_primary = if alias_target.global {
3456                unique_logical_type_candidate(self.type_candidates(file, &target_name))
3457            } else {
3458                self.resolve_unique_type_for_declaration(file, &primary, &target_name)
3459            };
3460            let Some(target_primary) = target_primary else {
3461                // A dependent or external RHS cannot be canonicalized from the
3462                // indexed graph. Preserve the alias's direct identity instead
3463                // of inventing a target from its source spelling.
3464                return Ok(primary);
3465            };
3466            let Some(target_arguments) = &alias_target.arguments else {
3467                return Ok(target_primary);
3468            };
3469            let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
3470                .ok_or(CppTemplateResolutionError::Substitution)?;
3471            return self.resolve_template_arguments_inner(
3472                file,
3473                target_primary,
3474                &target_arguments,
3475                seen_aliases,
3476            );
3477        }
3478
3479        let primary_fq_name = self
3480            .cpp_template_metadata
3481            .get(&primary)
3482            .map(|metadata| metadata.primary_fq_name.clone())
3483            .unwrap_or_else(|| primary.fq_name());
3484        let has_specialization_metadata = self
3485            .cpp_template_families
3486            .get(&primary_fq_name)
3487            .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
3488        if !has_specialization_metadata {
3489            return Ok(primary);
3490        }
3491        self.select_template_specialization(file, &primary, arguments)
3492    }
3493
3494    fn select_template_specialization(
3495        &self,
3496        file: &ProjectFile,
3497        resolved: &CodeUnit,
3498        explicit_arguments: &[CppTemplateExpression],
3499    ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3500        let primary_fq_name = self
3501            .cpp_template_metadata
3502            .get(resolved)
3503            .map(|metadata| metadata.primary_fq_name.clone())
3504            .unwrap_or_else(|| resolved.fq_name());
3505        let family = self
3506            .cpp_template_families
3507            .get(&primary_fq_name)
3508            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3509        let primary_candidates = family
3510            .iter()
3511            .filter_map(|unit| {
3512                let metadata = self.cpp_template_metadata.get(unit)?;
3513                (metadata.specialization_arguments.is_empty() && self.is_visible(file, unit))
3514                    .then_some((unit, metadata))
3515            })
3516            .collect::<Vec<_>>();
3517        let primary_unit = primary_candidates
3518            .iter()
3519            .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
3520            .or_else(|| {
3521                primary_candidates
3522                    .iter()
3523                    .map(|(unit, _)| *unit)
3524                    .min_by_key(|unit| {
3525                        (
3526                            unit.source().to_string(),
3527                            unit.signature().unwrap_or_default(),
3528                        )
3529                    })
3530            })
3531            .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3532        let primary_parameters =
3533            cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
3534                .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3535        let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
3536            .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3537
3538        let mut applicable = Vec::new();
3539        for unit in family {
3540            let Some(metadata) = self.cpp_template_metadata.get(unit) else {
3541                continue;
3542            };
3543            if metadata.specialization_arguments.is_empty() || !self.is_visible(file, unit) {
3544                continue;
3545            }
3546            if !cpp_specialization_matches(metadata, &expanded) {
3547                continue;
3548            }
3549            applicable.push((unit, metadata));
3550        }
3551        if applicable.is_empty() {
3552            return Ok(primary_unit.clone());
3553        }
3554
3555        // A scalar constraint count cannot represent C++ partial ordering:
3556        // e.g. `<T*, U>` and `<T, int>` are incomparable for `<int*, int>`.
3557        // Select only a logical candidate whose structural pattern is strictly
3558        // more specialized than every other distinct applicable candidate.
3559        let winners = applicable
3560            .iter()
3561            .filter(|(candidate, candidate_metadata)| {
3562                applicable.iter().all(|(other, other_metadata)| {
3563                    same_visible_symbol(candidate, other)
3564                        || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
3565                })
3566            })
3567            .copied()
3568            .collect::<Vec<_>>();
3569        let Some((selected, _)) = winners.first() else {
3570            // Mutually incomparable applicable candidates: every one of them
3571            // is a live contender.
3572            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
3573                candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
3574            });
3575        };
3576        if winners
3577            .iter()
3578            .any(|(unit, _)| !same_visible_symbol(unit, selected))
3579        {
3580            return Err(CppTemplateResolutionError::AmbiguousSpecialization {
3581                candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
3582            });
3583        }
3584        Ok((*selected).clone())
3585    }
3586
3587    pub fn resolve_type_components_lexically(
3588        &self,
3589        analyzer: &CppGraphSource<'_>,
3590        file: &ProjectFile,
3591        components: &[String],
3592        global: bool,
3593        lexical_scope: &[String],
3594    ) -> LexicalTypeResolution {
3595        self.resolve_type_components_lexically_inner(
3596            analyzer,
3597            file,
3598            components,
3599            global,
3600            lexical_scope,
3601            TypeCandidateResolution::Canonical,
3602        )
3603    }
3604
3605    pub fn resolve_type_components_lexically_for_forward(
3606        &self,
3607        analyzer: &CppGraphSource<'_>,
3608        file: &ProjectFile,
3609        components: &[String],
3610        global: bool,
3611        lexical_scope: &[String],
3612    ) -> LexicalTypeResolution {
3613        self.resolve_type_components_lexically_inner(
3614            analyzer,
3615            file,
3616            components,
3617            global,
3618            lexical_scope,
3619            TypeCandidateResolution::PreserveAlias,
3620        )
3621    }
3622
3623    pub fn resolve_type_components_lexically_for_target(
3624        &self,
3625        analyzer: &CppGraphSource<'_>,
3626        file: &ProjectFile,
3627        components: &[String],
3628        global: bool,
3629        lexical_scope: &[String],
3630        target: &CodeUnit,
3631    ) -> LexicalTypeResolution {
3632        #[cfg(any(test, feature = "test-support"))]
3633        self.target_preserving_type_resolution_count
3634            .fetch_add(1, Ordering::Relaxed);
3635        self.resolve_type_components_lexically_inner(
3636            analyzer,
3637            file,
3638            components,
3639            global,
3640            lexical_scope,
3641            TypeCandidateResolution::PreserveTarget(target),
3642        )
3643    }
3644
3645    pub fn coarse_unqualified_type_reference_may_resolve(
3646        &self,
3647        file: &ProjectFile,
3648        name: &str,
3649    ) -> bool {
3650        if name.is_empty() {
3651            return true;
3652        }
3653        self.visible_identifier_candidates(file, name)
3654            .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
3655            || self.visible_parser_alias_name_is_visible(file, name)
3656    }
3657
3658    #[allow(clippy::too_many_arguments)]
3659    pub fn structured_type_reference_may_resolve_to_target(
3660        &self,
3661        analyzer: &CppGraphSource<'_>,
3662        file: &ProjectFile,
3663        components: &[String],
3664        global: bool,
3665        lexical_scope: &[String],
3666        target: &CodeUnit,
3667    ) -> bool {
3668        if components.is_empty() {
3669            return true;
3670        }
3671        let Some(terminal) = components.last() else {
3672            return true;
3673        };
3674        let parser_alias_visible = self.visible_parser_alias_name_is_visible(file, terminal);
3675        if parser_alias_visible
3676            && self.parser_alias_resolves_to_type(analyzer, file, terminal, target)
3677        {
3678            return true;
3679        }
3680        let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
3681            .map(|qualified| qualified.join("::"))
3682            .collect::<Vec<_>>();
3683        let target_name = cpp_name_for(target);
3684        if qualified_tiers
3685            .iter()
3686            .any(|qualified| qualified == &target_name)
3687        {
3688            return true;
3689        }
3690
3691        let mut saw_shape_candidate = parser_alias_visible;
3692        for candidate in self.visible_identifier_candidates(file, terminal) {
3693            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
3694            {
3695                continue;
3696            }
3697            let candidate_name = cpp_name_for(candidate);
3698            let shape_matches = if global || components.len() > 1 {
3699                qualified_tiers
3700                    .iter()
3701                    .any(|qualified| qualified == &candidate_name)
3702            } else {
3703                true
3704            };
3705            if !shape_matches {
3706                continue;
3707            }
3708            saw_shape_candidate = true;
3709            if same_visible_symbol(candidate, target)
3710                || self.compatible_primary_template_redeclarations(candidate, target)
3711                || (declared_type_alias(analyzer, candidate)
3712                    && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
3713            {
3714                return true;
3715            }
3716        }
3717
3718        !saw_shape_candidate
3719    }
3720
3721    pub fn target_preserving_reference_namespace(
3722        &self,
3723        analyzer: &CppGraphSource<'_>,
3724        file: &ProjectFile,
3725        identifier: &str,
3726        target: &CodeUnit,
3727    ) -> Option<Vec<String>> {
3728        let mut namespace = None;
3729        for candidate in self.visible_identifier_candidates(file, identifier) {
3730            if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
3731            {
3732                continue;
3733            }
3734            if !(same_visible_symbol(candidate, target)
3735                || self.compatible_primary_template_redeclarations(candidate, target)
3736                || declared_type_alias(analyzer, candidate)
3737                    && self.structured_alias_primary_preserves_target(
3738                        analyzer, file, candidate, target,
3739                    ))
3740            {
3741                continue;
3742            }
3743            if namespace
3744                .as_ref()
3745                .is_some_and(|existing| existing != candidate.package_name())
3746            {
3747                return None;
3748            }
3749            namespace = Some(candidate.package_name().to_string());
3750        }
3751        let namespace = namespace?;
3752        Some(
3753            brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
3754                brokk_bifrost_core::analyzer::Language::Cpp,
3755                &namespace,
3756            ),
3757        )
3758    }
3759
3760    pub fn resolve_imported_type_candidate(
3761        &self,
3762        analyzer: &CppGraphSource<'_>,
3763        file: &ProjectFile,
3764        target: &CodeUnit,
3765        target_components: &[String],
3766        direct_target: Option<&CodeUnit>,
3767        preserve_alias: bool,
3768    ) -> LexicalTypeResolution {
3769        let candidates = [target];
3770        let resolution = if preserve_alias {
3771            TypeCandidateResolution::PreserveAlias
3772        } else {
3773            direct_target.map_or(
3774                TypeCandidateResolution::Canonical,
3775                TypeCandidateResolution::PreserveTarget,
3776            )
3777        };
3778        // One candidate goes in, so a failure here is never "choose one of
3779        // these": it is the alias chain leaving the index, which must answer
3780        // missing rather than ambiguous (#1828).
3781        match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
3782            Ok(unit) => LexicalTypeResolution::Resolved {
3783                unit,
3784                components: target_components.to_vec(),
3785                candidates: vec![target.clone()],
3786            },
3787            Err(failure) => failure.lexical_resolution(),
3788        }
3789    }
3790
3791    fn resolve_type_components_lexically_inner(
3792        &self,
3793        analyzer: &CppGraphSource<'_>,
3794        file: &ProjectFile,
3795        components: &[String],
3796        global: bool,
3797        lexical_scope: &[String],
3798        resolution: TypeCandidateResolution<'_>,
3799    ) -> LexicalTypeResolution {
3800        if components.is_empty() {
3801            return LexicalTypeResolution::Missing;
3802        }
3803        // A C++ class injects its own name into the class scope.  The indexed
3804        // FqName for that declaration is the class path itself (for example,
3805        // `n::raw_hash_set`), not a synthetic child named
3806        // `n::raw_hash_set::raw_hash_set`.  Ordinary lexical tiers append the
3807        // requested identifier to every scope component, so they cannot
3808        // represent that injected binding when the enclosing class is the
3809        // closest scope.  Recover the binding from the structured class path
3810        // before allowing lookup to fall through to an outer same-spelled
3811        // declaration.
3812        let mut injected = self.resolve_injected_class_name(
3813            analyzer,
3814            file,
3815            components,
3816            global,
3817            lexical_scope,
3818            resolution,
3819        );
3820        for (tier_index, qualified) in
3821            lexical_component_tiers(components, global, lexical_scope).enumerate()
3822        {
3823            let prefix_len = qualified.len().saturating_sub(components.len());
3824            if injected
3825                .as_ref()
3826                .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
3827            {
3828                return injected
3829                    .take()
3830                    .expect("injected class resolution was just present")
3831                    .1;
3832            }
3833            let qualified_name = qualified.join("::");
3834            let candidates = self
3835                .type_candidates(file, &qualified_name)
3836                .into_iter()
3837                .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
3838                .collect::<Vec<_>>();
3839            if candidates.is_empty() {
3840                if tier_index == 0 && !global && components.len() == 1 {
3841                    match self.resolve_inherited_type_for_lexical_scope(
3842                        analyzer,
3843                        file,
3844                        lexical_scope,
3845                        &components[0],
3846                        resolution,
3847                    ) {
3848                        LexicalTypeResolution::Missing => {}
3849                        inherited => return inherited,
3850                    }
3851                }
3852                continue;
3853            }
3854            let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
3855                Ok(unit) => unit,
3856                Err(failure) => return failure.lexical_resolution(),
3857            };
3858            return LexicalTypeResolution::Resolved {
3859                unit,
3860                components: qualified,
3861                candidates: candidates.into_iter().cloned().collect(),
3862            };
3863        }
3864        LexicalTypeResolution::Missing
3865    }
3866
3867    fn resolve_injected_class_name(
3868        &self,
3869        analyzer: &CppGraphSource<'_>,
3870        file: &ProjectFile,
3871        components: &[String],
3872        global: bool,
3873        lexical_scope: &[String],
3874        resolution: TypeCandidateResolution<'_>,
3875    ) -> Option<(usize, LexicalTypeResolution)> {
3876        if global
3877            || components.len() != 1
3878            || file.rel_path().extension().is_some_and(|ext| ext == "c")
3879            || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
3880        {
3881            return None;
3882        }
3883        let name = components.first()?;
3884        let mut matches: Vec<&CodeUnit> = Vec::new();
3885        let mut owner_len = 0;
3886        for candidate in self.visible_identifier_candidates(file, name) {
3887            if !candidate.is_class()
3888                || declared_type_alias(analyzer, candidate)
3889                || candidate.identifier() != name
3890            {
3891                continue;
3892            }
3893            let candidate_scope = canonical_cpp_scope_components(candidate);
3894            if candidate_scope.len() > lexical_scope.len()
3895                || !lexical_scope.starts_with(&candidate_scope)
3896                || candidate_scope.last().is_none_or(|last| last != name)
3897            {
3898                continue;
3899            }
3900            if candidate_scope.len() > owner_len {
3901                owner_len = candidate_scope.len();
3902                matches.clear();
3903            }
3904            if candidate_scope.len() == owner_len
3905                && !matches
3906                    .iter()
3907                    .any(|existing| same_logical_symbol(existing, candidate))
3908            {
3909                matches.push(candidate);
3910            }
3911        }
3912        if matches.is_empty() {
3913            return None;
3914        }
3915        // A same-named class at the current lexical boundary is already
3916        // represented by the ordinary namespace/class tier.  The injected
3917        // recovery is only needed when lookup is occurring inside a nested
3918        // class, where the enclosing class name is injected across that
3919        // additional class boundary.  Keeping this boundary strict avoids
3920        // treating qualified receiver/static-qualifier context as an
3921        // injected-name reference.
3922        if owner_len >= lexical_scope.len() {
3923            return None;
3924        }
3925        let owner_components = lexical_scope[..owner_len].to_vec();
3926        let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
3927            Ok(unit) => LexicalTypeResolution::Resolved {
3928                unit,
3929                components: owner_components,
3930                candidates: matches.into_iter().cloned().collect(),
3931            },
3932            Err(failure) => failure.lexical_resolution(),
3933        };
3934        Some((owner_len, resolution))
3935    }
3936
3937    fn resolve_inherited_type_for_lexical_scope(
3938        &self,
3939        analyzer: &CppGraphSource<'_>,
3940        file: &ProjectFile,
3941        lexical_scope: &[String],
3942        name: &str,
3943        resolution: TypeCandidateResolution<'_>,
3944    ) -> LexicalTypeResolution {
3945        let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
3946            return LexicalTypeResolution::Missing;
3947        };
3948        let lexical_owner_name = lexical_scope.join("::");
3949        if lexical_owner_name.is_empty() {
3950            return LexicalTypeResolution::Missing;
3951        }
3952        let owner_candidates = self
3953            .type_candidates(file, &lexical_owner_name)
3954            .into_iter()
3955            .filter(|candidate| {
3956                canonical_cpp_name_matches(candidate, &lexical_owner_name)
3957                    && !declared_type_alias(analyzer, candidate)
3958            })
3959            .collect::<Vec<_>>();
3960        if owner_candidates.is_empty() {
3961            return LexicalTypeResolution::Missing;
3962        }
3963        let Some(lexical_owner) = unique_logical_type_candidate(owner_candidates) else {
3964            return LexicalTypeResolution::Ambiguous;
3965        };
3966
3967        let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
3968        let mut visited_owners = HashSet::default();
3969        while !frontier.is_empty() {
3970            let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
3971            let mut next_frontier = Vec::new();
3972            for owner in frontier {
3973                if !visited_owners.insert(owner.fq_name()) {
3974                    continue;
3975                }
3976                let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
3977                let candidates = self
3978                    .type_candidates(file, &qualified_name)
3979                    .into_iter()
3980                    .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
3981                    .collect::<Vec<_>>();
3982                if candidates.is_empty() {
3983                    for ancestor in hierarchy.get_direct_ancestors(&owner) {
3984                        if !next_frontier
3985                            .iter()
3986                            .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
3987                        {
3988                            next_frontier.push(ancestor);
3989                        }
3990                    }
3991                    continue;
3992                }
3993                let unit =
3994                    match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
3995                        Ok(unit) => unit,
3996                        Err(failure) => return failure.lexical_resolution(),
3997                    };
3998                level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
3999            }
4000            if let Some((unit, candidates)) = level_matches.first().cloned() {
4001                let Some(first_declaration) = candidates.first() else {
4002                    return LexicalTypeResolution::Ambiguous;
4003                };
4004                if !level_matches.iter().all(|(_, declarations)| {
4005                    declarations
4006                        .iter()
4007                        .all(|declaration| same_logical_symbol(first_declaration, declaration))
4008                }) {
4009                    return LexicalTypeResolution::Ambiguous;
4010                }
4011                let mut components = lexical_scope.to_vec();
4012                components.push(name.to_string());
4013                return LexicalTypeResolution::Resolved {
4014                    unit,
4015                    components,
4016                    candidates,
4017                };
4018            }
4019            frontier = next_frontier;
4020        }
4021        LexicalTypeResolution::Missing
4022    }
4023
4024    /// Resolve a base class through its injected class name at the nearest
4025    /// inheritance tier. Distinct same-named bases at that tier are ambiguous.
4026    pub fn inherited_injected_class_owner(
4027        &self,
4028        analyzer: &CppGraphSource<'_>,
4029        file: &ProjectFile,
4030        enclosing_owner: &CodeUnit,
4031        injected_name: &str,
4032    ) -> Option<CodeUnit> {
4033        let hierarchy = analyzer.type_hierarchy_provider()?;
4034        let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
4035        let mut visited = HashSet::default();
4036        while !frontier.is_empty() {
4037            let mut level_matches = Vec::new();
4038            let mut next_frontier = Vec::new();
4039            for raw_owner in frontier {
4040                let owner = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)?;
4041                if !visited.insert(owner.clone()) {
4042                    continue;
4043                }
4044                if owner.identifier() == injected_name
4045                    && !level_matches
4046                        .iter()
4047                        .any(|existing| same_logical_symbol(existing, &owner))
4048                {
4049                    level_matches.push(owner.clone());
4050                }
4051                next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
4052            }
4053            if let Some(first) = level_matches.first() {
4054                return level_matches
4055                    .iter()
4056                    .all(|candidate| same_logical_symbol(candidate, first))
4057                    .then(|| first.clone());
4058            }
4059            frontier = next_frontier;
4060        }
4061        None
4062    }
4063
4064    /// The one type the candidates name under `resolution`, or why they do not
4065    /// name one. The two preserving modes only ever reject candidates that
4066    /// disagree with each other, which is ambiguity; canonicalization can also
4067    /// fail because the alias chain leaves the index (#1828).
4068    fn resolve_type_candidates(
4069        &self,
4070        analyzer: &CppGraphSource<'_>,
4071        file: &ProjectFile,
4072        candidates: &[&CodeUnit],
4073        resolution: TypeCandidateResolution<'_>,
4074    ) -> Result<CodeUnit, TypeCandidateFailure> {
4075        match resolution {
4076            TypeCandidateResolution::Canonical => {
4077                self.canonical_type_candidate_resolution(analyzer, file, candidates)
4078            }
4079            TypeCandidateResolution::PreserveAlias => {
4080                unique_type_candidate_preserving_alias(analyzer, candidates)
4081                    .ok_or(TypeCandidateFailure::Ambiguous)
4082            }
4083            TypeCandidateResolution::PreserveTarget(target) => self
4084                .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
4085                .ok_or(TypeCandidateFailure::Ambiguous),
4086        }
4087    }
4088
4089    pub fn resolve_callable_value_components_lexically(
4090        &self,
4091        analyzer: &CppGraphSource<'_>,
4092        file: &ProjectFile,
4093        owner_components: &[String],
4094        member_name: &str,
4095        global: bool,
4096        lexical_scope: &[String],
4097    ) -> LexicalCallableValueResolution {
4098        if owner_components.is_empty() || member_name.is_empty() {
4099            return LexicalCallableValueResolution::Missing;
4100        }
4101        for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
4102            let owner_name = qualified_owner.join("::");
4103            let type_candidates = self
4104                .type_candidates(file, &owner_name)
4105                .into_iter()
4106                .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
4107                .collect::<Vec<_>>();
4108            let resolved_type = if type_candidates.is_empty() {
4109                None
4110            } else {
4111                let Some(unit) =
4112                    self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
4113                else {
4114                    return LexicalCallableValueResolution::Ambiguous;
4115                };
4116                Some(unit)
4117            };
4118
4119            let mut qualified_callable = qualified_owner;
4120            qualified_callable.push(member_name.to_string());
4121            let callable_name = qualified_callable.join("::");
4122            let free_function = self
4123                .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
4124                .into_iter()
4125                .find(|candidate| {
4126                    canonical_cpp_name_matches(candidate, &callable_name)
4127                        && type_owner_of(analyzer, candidate).is_none()
4128                })
4129                .cloned();
4130
4131            match (resolved_type, free_function) {
4132                (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
4133                (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
4134                (None, Some(function)) => {
4135                    return LexicalCallableValueResolution::FreeFunction(function);
4136                }
4137                (None, None) => {}
4138            }
4139        }
4140        LexicalCallableValueResolution::Missing
4141    }
4142
4143    fn resolve_type_for_declaration(
4144        &self,
4145        visible_from: &ProjectFile,
4146        declaration: &CodeUnit,
4147        raw_name: &str,
4148    ) -> Option<CodeUnit> {
4149        let normalized = normalize_reference_name(raw_name)?;
4150        if !normalized.contains("::")
4151            && let Some(namespace) = cpp_namespace_for(declaration)
4152        {
4153            for prefix in namespace_prefixes(&namespace) {
4154                let qualified = format!("{prefix}::{normalized}");
4155                if let Some(unit) = self
4156                    .type_candidates(visible_from, &qualified)
4157                    .into_iter()
4158                    .next()
4159                {
4160                    return Some(unit.clone());
4161                }
4162            }
4163        }
4164        self.resolve_type(visible_from, raw_name)
4165    }
4166
4167    fn resolve_unique_canonical_type_for_declaration(
4168        &self,
4169        analyzer: &CppGraphSource<'_>,
4170        visible_from: &ProjectFile,
4171        declaration: &CodeUnit,
4172        raw_name: &str,
4173    ) -> Option<CodeUnit> {
4174        let mut current =
4175            self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
4176        let mut seen_aliases = HashSet::default();
4177        loop {
4178            let Some(target) = self.structured_alias_target(analyzer, &current) else {
4179                return current.is_class().then_some(current);
4180            };
4181            if matches!(target, StructuredAliasTarget::Builtin) {
4182                return current.is_class().then_some(current);
4183            }
4184            if !seen_aliases.insert(current.clone()) {
4185                return None;
4186            }
4187            current = self.resolve_structured_alias_target(visible_from, &current, &target)?;
4188        }
4189    }
4190
4191    pub fn canonical_type_unit(
4192        &self,
4193        analyzer: &CppGraphSource<'_>,
4194        visible_from: &ProjectFile,
4195        unit: &CodeUnit,
4196    ) -> Option<CodeUnit> {
4197        self.canonical_type_resolution(analyzer, visible_from, unit)
4198            .ok()
4199    }
4200
4201    /// Follow `unit`'s alias chain to the class it names, or report why the
4202    /// chain does not end at one indexed class.
4203    ///
4204    /// A chain that leaves the index - an alias to a template parameter, to a
4205    /// standard-library type, or to any other declaration the workspace does
4206    /// not hold - is `Unresolvable`, not `Ambiguous` (#1828). So is a cycle:
4207    /// there is still nothing to choose between.
4208    fn canonical_type_resolution(
4209        &self,
4210        analyzer: &CppGraphSource<'_>,
4211        visible_from: &ProjectFile,
4212        unit: &CodeUnit,
4213    ) -> Result<CodeUnit, TypeCandidateFailure> {
4214        let mut current = unit.clone();
4215        let mut seen_aliases = HashSet::default();
4216        loop {
4217            let Some(target) = self.structured_alias_target(analyzer, &current) else {
4218                return current
4219                    .is_class()
4220                    .then_some(current)
4221                    .ok_or(TypeCandidateFailure::Unresolvable);
4222            };
4223            if matches!(target, StructuredAliasTarget::Builtin) {
4224                return current
4225                    .is_class()
4226                    .then_some(current)
4227                    .ok_or(TypeCandidateFailure::Unresolvable);
4228            }
4229            if !seen_aliases.insert(current.clone()) {
4230                return Err(TypeCandidateFailure::Unresolvable);
4231            }
4232            current = self.structured_alias_target_resolution(visible_from, &current, &target)?;
4233        }
4234    }
4235
4236    pub fn canonical_visible_full_type_unit(
4237        &self,
4238        analyzer: &CppGraphSource<'_>,
4239        visible_from: &ProjectFile,
4240        unit: &CodeUnit,
4241    ) -> Option<CodeUnit> {
4242        let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
4243        if cpp_class_declaration_strength(analyzer, &canonical)
4244            != CppClassDeclarationStrength::Forward
4245        {
4246            return Some(canonical);
4247        }
4248        let mut full = Vec::new();
4249        for candidate in self
4250            .visible_identifier_candidates(visible_from, canonical.identifier())
4251            .filter(|candidate| {
4252                candidate.is_class()
4253                    && candidate.fq_name() == canonical.fq_name()
4254                    && cpp_class_declaration_strength(analyzer, candidate)
4255                        == CppClassDeclarationStrength::Full
4256            })
4257        {
4258            if !full.iter().any(|existing| same_symbol(existing, candidate)) {
4259                full.push(candidate.clone());
4260            }
4261        }
4262        match full.len() {
4263            0 => Some(canonical),
4264            1 => full.pop(),
4265            _ => None,
4266        }
4267    }
4268
4269    fn resolve_structured_alias_target(
4270        &self,
4271        visible_from: &ProjectFile,
4272        declaration: &CodeUnit,
4273        target: &StructuredAliasTarget,
4274    ) -> Option<CodeUnit> {
4275        self.structured_alias_target_resolution(visible_from, declaration, target)
4276            .ok()
4277    }
4278
4279    fn structured_alias_target_resolution(
4280        &self,
4281        visible_from: &ProjectFile,
4282        declaration: &CodeUnit,
4283        target: &StructuredAliasTarget,
4284    ) -> Result<CodeUnit, TypeCandidateFailure> {
4285        let primary =
4286            self.structured_alias_primary_resolution(visible_from, declaration, target)?;
4287        let StructuredAliasTarget::Named { arguments, .. } = target else {
4288            return Err(TypeCandidateFailure::Unresolvable);
4289        };
4290        match arguments {
4291            Some(arguments) => self
4292                .resolve_template_arguments(visible_from, primary, arguments)
4293                .map_err(|error| match error {
4294                    CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
4295                        TypeCandidateFailure::Ambiguous
4296                    }
4297                    _ => TypeCandidateFailure::Unresolvable,
4298                }),
4299            None => Ok(primary),
4300        }
4301    }
4302
4303    fn resolve_structured_alias_primary(
4304        &self,
4305        visible_from: &ProjectFile,
4306        declaration: &CodeUnit,
4307        target: &StructuredAliasTarget,
4308    ) -> Option<CodeUnit> {
4309        self.structured_alias_primary_resolution(visible_from, declaration, target)
4310            .ok()
4311    }
4312
4313    fn structured_alias_primary_resolution(
4314        &self,
4315        visible_from: &ProjectFile,
4316        declaration: &CodeUnit,
4317        target: &StructuredAliasTarget,
4318    ) -> Result<CodeUnit, TypeCandidateFailure> {
4319        let StructuredAliasTarget::Named {
4320            components, global, ..
4321        } = target
4322        else {
4323            return Err(TypeCandidateFailure::Unresolvable);
4324        };
4325        let qualified = components.join("::");
4326        let candidates = if *global {
4327            self.type_candidates(visible_from, &qualified)
4328        } else {
4329            self.type_candidates_for_declaration(visible_from, declaration, &qualified)
4330        };
4331        logical_type_candidate(candidates)
4332    }
4333
4334    pub fn structured_alias_primary_preserves_target(
4335        &self,
4336        analyzer: &CppGraphSource<'_>,
4337        visible_from: &ProjectFile,
4338        candidate: &CodeUnit,
4339        target: &CodeUnit,
4340    ) -> bool {
4341        let mut current = candidate.clone();
4342        let mut seen = HashSet::default();
4343        let mut matched_target = false;
4344        loop {
4345            if same_visible_symbol(&current, target)
4346                || self.compatible_primary_template_redeclarations(&current, target)
4347            {
4348                matched_target = true;
4349            }
4350            if !seen.insert(current.clone()) {
4351                return false;
4352            }
4353            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
4354                return matched_target;
4355            };
4356            if matches!(alias_target, StructuredAliasTarget::Builtin) {
4357                return matched_target;
4358            };
4359            let Some(primary) =
4360                self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
4361            else {
4362                // A dependent member target such as `Detector<T>::type`
4363                // cannot be reduced to an indexed primary, but a preceding
4364                // structured alias hop may already have proven the requested
4365                // alias identity. Cycles still resolve a primary and are
4366                // rejected by `seen` above.
4367                return matched_target;
4368            };
4369            current = primary;
4370        }
4371    }
4372
4373    pub fn structured_class_alias_resolves_to_target(
4374        &self,
4375        analyzer: &CppGraphSource<'_>,
4376        visible_from: &ProjectFile,
4377        alias: &CodeUnit,
4378        target: &CodeUnit,
4379    ) -> bool {
4380        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4381            return false;
4382        };
4383        let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
4384            return false;
4385        };
4386        let StructuredAliasTarget::Named {
4387            components, global, ..
4388        } = &alias_target
4389        else {
4390            return false;
4391        };
4392        let lexical_scope = canonical_cpp_scope_components(&owner);
4393        match self.resolve_type_components_lexically_for_target(
4394            analyzer,
4395            visible_from,
4396            components,
4397            *global,
4398            &lexical_scope,
4399            target,
4400        ) {
4401            LexicalTypeResolution::Resolved {
4402                unit, candidates, ..
4403            } => {
4404                same_visible_symbol(&unit, target)
4405                    || self.same_template_member_identity(analyzer, &unit, target)
4406                    || candidates.iter().any(|candidate| {
4407                        same_visible_symbol(candidate, target)
4408                            || self.same_template_member_identity(analyzer, candidate, target)
4409                    })
4410            }
4411            LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
4412                self.structured_alias_primary_preserves_target(
4413                    analyzer,
4414                    visible_from,
4415                    alias,
4416                    target,
4417                ) || self.flattened_macro_namespace_alias_target_matches(
4418                    analyzer,
4419                    visible_from,
4420                    alias,
4421                    &alias_target,
4422                    target,
4423                )
4424            }
4425        }
4426    }
4427
4428    /// Return true when a class-owned alias names the requested type as one
4429    /// structured qualifier in its target path.
4430    ///
4431    /// A dependent target such as `Primary<T>::Type` cannot resolve to one
4432    /// indexed class. Forward lookup can still retain `Primary` as its bounded
4433    /// canonical identity. Inverse lookup needs the same evidence when later
4434    /// references use only the alias spelling.
4435    pub fn structured_class_alias_path_preserves_target(
4436        &self,
4437        analyzer: &CppGraphSource<'_>,
4438        visible_from: &ProjectFile,
4439        alias: &CodeUnit,
4440        target: &CodeUnit,
4441    ) -> bool {
4442        let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4443            return false;
4444        };
4445        let Some(StructuredAliasTarget::Named {
4446            components, global, ..
4447        }) = self.structured_alias_target(analyzer, alias)
4448        else {
4449            return false;
4450        };
4451        let lexical_scope = canonical_cpp_scope_components(&owner);
4452        (1..components.len()).rev().any(|component_count| {
4453            matches!(
4454                self.resolve_type_components_lexically_for_target(
4455                    analyzer,
4456                    visible_from,
4457                    &components[..component_count],
4458                    global,
4459                    &lexical_scope,
4460                    target,
4461                ),
4462                LexicalTypeResolution::Resolved {
4463                    ref unit,
4464                    ref candidates,
4465                    ..
4466                } if same_visible_symbol(unit, target)
4467                    || self.same_template_member_identity(analyzer, unit, target)
4468                    || candidates.iter().any(|candidate| {
4469                        same_visible_symbol(candidate, target)
4470                            || self.same_template_member_identity(analyzer, candidate, target)
4471                    })
4472            )
4473        })
4474    }
4475
4476    fn flattened_macro_namespace_alias_target_matches(
4477        &self,
4478        analyzer: &CppGraphSource<'_>,
4479        visible_from: &ProjectFile,
4480        alias: &CodeUnit,
4481        alias_target: &StructuredAliasTarget,
4482        target: &CodeUnit,
4483    ) -> bool {
4484        let StructuredAliasTarget::Named {
4485            components,
4486            global: false,
4487            arguments: None,
4488        } = alias_target
4489        else {
4490            return false;
4491        };
4492        let Some((target_name, namespace_components)) = components.split_last() else {
4493            return false;
4494        };
4495        if namespace_components.is_empty()
4496            || target_name != target.identifier()
4497            || alias.source() != target.source()
4498            || alias.source() != visible_from
4499            || !target.is_class()
4500            || declared_type_alias(analyzer, target)
4501        {
4502            return false;
4503        }
4504        if self
4505            .resolve_structured_alias_target(visible_from, alias, alias_target)
4506            .is_some()
4507        {
4508            return false;
4509        }
4510
4511        let alias_ranges = analyzer.ranges(alias);
4512        let target_ranges = analyzer.ranges(target);
4513        if alias_ranges.is_empty() || target_ranges.is_empty() {
4514            return false;
4515        }
4516        let alias_start = alias_ranges
4517            .iter()
4518            .map(|range| range.start_byte)
4519            .min()
4520            .expect("non-empty alias ranges have a minimum");
4521        let Some(prepared) = self.cpp.prepared_syntax(target.source()) else {
4522            return false;
4523        };
4524        let root = prepared.tree().root_node();
4525        let has_matching_declaration = target_ranges
4526            .iter()
4527            .filter(|range| range.end_byte <= alias_start)
4528            .filter_map(|range| node_for_exact_range(root, range))
4529            .any(|node| {
4530                flattened_macro_namespace_components(node, prepared.source())
4531                    .is_some_and(|recovered| recovered == namespace_components)
4532            });
4533        if !has_matching_declaration {
4534            return false;
4535        }
4536
4537        let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
4538        let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
4539        guard_requirement_sets_match(&alias_guards, &target_guards)
4540    }
4541
4542    pub fn template_alias_arguments_preserve_target(
4543        &self,
4544        analyzer: &CppGraphSource<'_>,
4545        visible_from: &ProjectFile,
4546        alias: &CodeUnit,
4547        arguments: &[CppTemplateExpression],
4548        target: &CodeUnit,
4549    ) -> bool {
4550        let Some(metadata) = self.cpp_template_metadata.get(alias) else {
4551            return false;
4552        };
4553        if metadata.alias_target.is_none()
4554            || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
4555        {
4556            return false;
4557        }
4558        self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
4559    }
4560
4561    pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
4562        self.cpp_template_metadata
4563            .get(unit)
4564            .is_some_and(|metadata| metadata.specialization_arguments.is_empty())
4565    }
4566
4567    pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
4568        self.cpp_template_metadata
4569            .get(unit)
4570            .is_some_and(|metadata| !metadata.specialization_arguments.is_empty())
4571    }
4572
4573    pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
4574        same_visible_symbol(left, right)
4575            || self.compatible_primary_template_redeclarations(left, right)
4576    }
4577
4578    pub fn same_template_member_identity(
4579        &self,
4580        analyzer: &CppGraphSource<'_>,
4581        left: &CodeUnit,
4582        right: &CodeUnit,
4583    ) -> bool {
4584        if same_visible_symbol(left, right) {
4585            return true;
4586        }
4587        if left.kind() != right.kind()
4588            || left.identifier() != right.identifier()
4589            || left.signature() != right.signature()
4590        {
4591            return false;
4592        }
4593        let (Some(left_owner), Some(right_owner)) =
4594            (analyzer.parent_of(left), analyzer.parent_of(right))
4595        else {
4596            return false;
4597        };
4598        left_owner.is_class()
4599            && right_owner.is_class()
4600            && self.same_template_owner_identity(&left_owner, &right_owner)
4601    }
4602
4603    fn unique_canonical_type_candidate(
4604        &self,
4605        analyzer: &CppGraphSource<'_>,
4606        visible_from: &ProjectFile,
4607        candidates: &[&CodeUnit],
4608    ) -> Option<CodeUnit> {
4609        self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
4610            .ok()
4611    }
4612
4613    fn canonical_type_candidate_resolution(
4614        &self,
4615        analyzer: &CppGraphSource<'_>,
4616        visible_from: &ProjectFile,
4617        candidates: &[&CodeUnit],
4618    ) -> Result<CodeUnit, TypeCandidateFailure> {
4619        let mut canonical = Vec::new();
4620        for candidate in candidates {
4621            let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
4622            if canonical
4623                .iter()
4624                .any(|existing| same_visible_symbol(existing, &resolved))
4625            {
4626                continue;
4627            }
4628            if let Some(existing) = canonical.iter_mut().find(|existing| {
4629                self.compatible_primary_template_redeclarations(existing, &resolved)
4630            }) {
4631                // A forward declaration and its full primary-template
4632                // definition are one C++ type even when they live in
4633                // different headers and alpha-rename their parameters. The
4634                // target-preserving path already reconciles this family; do
4635                // the same for ordinary canonical lookup so an out-of-line
4636                // member's lexical owner is not made ambiguous by its own
4637                // forward declaration. Retain the strongest physical
4638                // declaration for later owner/range queries.
4639                if matches!(
4640                    (
4641                        cpp_class_declaration_strength(analyzer, existing),
4642                        cpp_class_declaration_strength(analyzer, &resolved),
4643                    ),
4644                    (
4645                        CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
4646                        CppClassDeclarationStrength::Full,
4647                    ) | (
4648                        CppClassDeclarationStrength::Unknown,
4649                        CppClassDeclarationStrength::Forward,
4650                    )
4651                ) {
4652                    *existing = resolved;
4653                }
4654                continue;
4655            }
4656            canonical.push(resolved);
4657            if canonical.len() > 1 {
4658                return Err(TypeCandidateFailure::Ambiguous);
4659            }
4660        }
4661        canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
4662    }
4663
4664    pub fn unique_type_candidate_preserving_target(
4665        &self,
4666        analyzer: &CppGraphSource<'_>,
4667        visible_from: &ProjectFile,
4668        candidates: &[&CodeUnit],
4669        target: &CodeUnit,
4670    ) -> Option<CodeUnit> {
4671        // C++ headers often expose one logical type through mutually exclusive
4672        // physical declarations, for example a class in the fallback branch
4673        // and a `using` alias to the standard-library type in the configured
4674        // branch. The index intentionally retains both declarations so forward
4675        // lookup can report each target. Preserve the requested target when
4676        // that is the only ambiguity: every candidate has the same type kind,
4677        // exact canonical FQN, and source file, and the requested declaration
4678        // itself is one of the physical candidates. Do not merge same-named
4679        // declarations from different files or namespaces; those remain
4680        // ambiguous and fail closed below.
4681        if self.alternate_same_fqn_type_declarations(analyzer, candidates, target) {
4682            return Some(target.clone());
4683        }
4684        let mut resolved_candidates = Vec::new();
4685        for candidate in candidates {
4686            let resolved =
4687                self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)?;
4688            if resolved_candidates
4689                .iter()
4690                .any(|existing| same_visible_symbol(existing, &resolved))
4691            {
4692                continue;
4693            }
4694            resolved_candidates.push(resolved);
4695        }
4696        match resolved_candidates.as_slice() {
4697            [] => None,
4698            [single] => Some(single.clone()),
4699            // The branches disagree about what the name aliases. When they are
4700            // spellings of one entity (#1845) that disagreement is a build
4701            // configuration, not a choice between types, so it must not deny
4702            // the requested target its reference.
4703            _ => self
4704                .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
4705                .map(|_| target.clone()),
4706        }
4707    }
4708
4709    /// The declaration a same-file same-FQN family stands for when a reference
4710    /// names `target`, or `None` when the candidates are not one family or the
4711    /// family does not name `target`.
4712    ///
4713    /// A translation unit cannot hold two different types under one qualified
4714    /// name, so several same-kind declarations of one FQN in one file are
4715    /// alternate spellings of one entity - the configuration branches of an
4716    /// `#if` family, for example log4cxx's `logchar`, which aliases `char` in
4717    /// the UTF-8 branch and `UniChar` in the unichar branch. Their alias
4718    /// targets differ; canonicalizing each branch on its own and then demanding
4719    /// agreement reports an ambiguity that denies every declaration in the
4720    /// family its usages (#1845). The family names `target` when it declares
4721    /// it, or when one branch's alias chain reaches it.
4722    ///
4723    /// Declarations in different files or namespaces are distinct entities and
4724    /// are deliberately excluded: their disagreement is a real ambiguity.
4725    pub fn same_fqn_type_spelling_for_target<'b>(
4726        &self,
4727        analyzer: &CppGraphSource<'_>,
4728        visible_from: &ProjectFile,
4729        candidates: &[&'b CodeUnit],
4730        target: &CodeUnit,
4731    ) -> Option<&'b CodeUnit> {
4732        let [first, rest @ ..] = candidates else {
4733            return None;
4734        };
4735        if rest.is_empty()
4736            || !rest.iter().all(|candidate| {
4737                candidate.kind() == first.kind()
4738                    && candidate.fq_name() == first.fq_name()
4739                    && candidate.source() == first.source()
4740            })
4741        {
4742            return None;
4743        }
4744        candidates
4745            .iter()
4746            .copied()
4747            .find(|candidate| same_symbol(candidate, target))
4748            .or_else(|| {
4749                candidates.iter().copied().find(|candidate| {
4750                    self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
4751                        .is_some_and(|resolved| same_visible_symbol(&resolved, target))
4752                })
4753            })
4754    }
4755
4756    pub fn alternate_same_fqn_type_declarations(
4757        &self,
4758        analyzer: &CppGraphSource<'_>,
4759        candidates: &[&CodeUnit],
4760        target: &CodeUnit,
4761    ) -> bool {
4762        let Some(first) = candidates.first() else {
4763            return false;
4764        };
4765        let same_api = first.kind() == target.kind()
4766            && first.fq_name() == target.fq_name()
4767            && first.source() == target.source()
4768            && candidates.iter().all(|candidate| {
4769                candidate.kind() == target.kind()
4770                    && candidate.fq_name() == target.fq_name()
4771                    && candidate.source() == target.source()
4772            })
4773            && candidates
4774                .iter()
4775                .any(|candidate| same_symbol(candidate, target))
4776            && candidates
4777                .iter()
4778                .any(|candidate| !same_logical_symbol(candidate, target));
4779        if !same_api {
4780            return false;
4781        }
4782
4783        let requirements = candidates
4784            .iter()
4785            .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
4786            .collect::<Vec<_>>();
4787        requirements.len() > 1
4788            && requirements
4789                .iter()
4790                .all(|requirement| !requirement.is_empty())
4791            && requirements.iter().enumerate().all(|(index, left)| {
4792                requirements[index + 1..].iter().all(|right| {
4793                    left.iter().all(|(_, left_guards)| {
4794                        right.iter().all(|(_, right_guards)| {
4795                            merge_preprocessor_guards(left_guards, right_guards).is_none()
4796                        })
4797                    })
4798                })
4799            })
4800    }
4801
4802    fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
4803        let mut pending = vec![terms.to_vec()];
4804        while let Some(branch_terms) = pending.pop() {
4805            let mut normalized = Vec::new();
4806            let mut covers_branch = false;
4807            for term in branch_terms {
4808                if term.iter().any(|guard| term.contains(&guard.negated())) {
4809                    continue;
4810                }
4811                if term.is_empty() {
4812                    covers_branch = true;
4813                    break;
4814                }
4815                if !normalized.iter().any(|existing| existing == &term) {
4816                    normalized.push(term);
4817                }
4818            }
4819            if covers_branch {
4820                continue;
4821            }
4822            let Some(split_guard) = normalized
4823                .iter()
4824                .flat_map(|term| term.iter())
4825                .next()
4826                .cloned()
4827            else {
4828                return false;
4829            };
4830            let negated_guard = split_guard.negated();
4831            let mut when_defined = Vec::new();
4832            let mut when_undefined = Vec::new();
4833            for term in normalized {
4834                if term.contains(&negated_guard) {
4835                    // This term cannot hold when `split_guard` is true.
4836                } else if term.contains(&split_guard) {
4837                    let mut reduced = term.clone();
4838                    reduced.remove(&split_guard);
4839                    when_defined.push(reduced);
4840                } else {
4841                    when_defined.push(term.clone());
4842                }
4843                if term.contains(&split_guard) {
4844                    // This term cannot hold when `split_guard` is false.
4845                } else if term.contains(&negated_guard) {
4846                    let mut reduced = term;
4847                    reduced.remove(&negated_guard);
4848                    when_undefined.push(reduced);
4849                } else {
4850                    when_undefined.push(term);
4851                }
4852            }
4853            pending.push(when_defined);
4854            pending.push(when_undefined);
4855        }
4856        true
4857    }
4858
4859    /// The byte range of the one `#if` family with a terminal `#else` that holds
4860    /// every physical declaration of every candidate, or `None` when they do not
4861    /// share one such family.
4862    ///
4863    /// Guard terms alone cannot distinguish one `#if` family from separate blocks
4864    /// whose macros changed between declarations. Require every physical range to
4865    /// belong to one syntax-tree family with a terminal `#else` before the terms
4866    /// can prove branch coverage.
4867    fn declarations_share_exhaustive_conditional_family(
4868        &self,
4869        analyzer: &CppGraphSource<'_>,
4870        candidates: &[&CodeUnit],
4871    ) -> Option<(usize, usize)> {
4872        let mut family_range = None;
4873        for candidate in candidates {
4874            let prepared = self.cpp.prepared_syntax(candidate.source())?;
4875            let root = prepared.tree().root_node();
4876            let mut candidate_family = None;
4877            for range in analyzer.ranges(candidate) {
4878                let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
4879                let family = preprocessor_conditional_family_for_declaration(node)?;
4880                let key = (family.start_byte(), family.end_byte());
4881                if candidate_family.is_some_and(|existing| existing != key) {
4882                    return None;
4883                }
4884                candidate_family = Some(key);
4885            }
4886            let candidate_family = candidate_family?;
4887            if family_range.is_some_and(|existing| existing != candidate_family) {
4888                return None;
4889            }
4890            family_range = Some(candidate_family);
4891        }
4892        family_range
4893    }
4894
4895    pub fn complementary_same_fqn_type_declarations(
4896        &self,
4897        analyzer: &CppGraphSource<'_>,
4898        candidates: &[&CodeUnit],
4899        target: &CodeUnit,
4900    ) -> bool {
4901        if candidates.len() < 2
4902            || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
4903            || self
4904                .declarations_share_exhaustive_conditional_family(analyzer, candidates)
4905                .is_none()
4906        {
4907            return false;
4908        }
4909        Self::preprocessor_guard_terms_cover_all_paths(
4910            &self.declaration_family_guard_terms(analyzer, candidates),
4911        )
4912    }
4913
4914    fn declaration_family_guard_terms(
4915        &self,
4916        analyzer: &CppGraphSource<'_>,
4917        candidates: &[&CodeUnit],
4918    ) -> Vec<HashSet<PreprocessorGuard>> {
4919        candidates
4920            .iter()
4921            .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
4922            .map(|(_, guards)| guards)
4923            .collect()
4924    }
4925
4926    /// A callable name declared on every branch of one completed `#if`/`#else`
4927    /// family is declared on every configuration path, so a reference below the
4928    /// whole family sees one of the branches whatever the preprocessor decides.
4929    /// Answer the family's end byte: only past `#endif` is every branch's
4930    /// declaration behind the reference.
4931    ///
4932    /// This is the callable analogue of `complementary_same_fqn_type_declarations`
4933    /// and shares both of its primitives. It does not require two distinct
4934    /// `CodeUnit`s: branches that declare the same signature can collapse into
4935    /// one unit carrying one physical range per branch.
4936    ///
4937    /// The branches are alternate spellings of one declaration, never competing
4938    /// declarations, so only the first branch stands for the family. Reporting
4939    /// every branch as visible would turn a name the source declares exactly
4940    /// once into an ambiguity between build configurations.
4941    fn exhaustive_guard_family_activation(
4942        &self,
4943        analyzer: &CppGraphSource<'_>,
4944        prepared: &PreparedSyntaxTree,
4945        candidate: &CodeUnit,
4946        reference: &CallableReferenceContext<'_>,
4947    ) -> Option<usize> {
4948        // Branch coverage says nothing about scope: a block-local declaration
4949        // stays invisible however many branches declare it.
4950        if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
4951            return None;
4952        }
4953        let family = self
4954            .visible_identifier_candidates(candidate.source(), candidate.identifier())
4955            .filter(|peer| {
4956                peer.kind() == candidate.kind()
4957                    && peer.fq_name() == candidate.fq_name()
4958                    && peer.source() == candidate.source()
4959            })
4960            .collect::<Vec<_>>();
4961        let (_, family_end) =
4962            self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
4963        if !Self::preprocessor_guard_terms_cover_all_paths(
4964            &self.declaration_family_guard_terms(analyzer, &family),
4965        ) {
4966            return None;
4967        }
4968        // A reference whose own guards pick one branch already reaches that
4969        // branch through the ordinary same-guard path; the family must not
4970        // resurrect the branch the reference contradicts.
4971        if !declaration_guard_requirements(analyzer, self.cpp, candidate)
4972            .iter()
4973            .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
4974        {
4975            return None;
4976        }
4977        (first_declaration_byte(analyzer, candidate)?
4978            == family
4979                .iter()
4980                .filter_map(|peer| first_declaration_byte(analyzer, peer))
4981                .min()?)
4982        .then_some(family_end)
4983    }
4984
4985    fn type_candidate_preserving_target(
4986        &self,
4987        analyzer: &CppGraphSource<'_>,
4988        visible_from: &ProjectFile,
4989        candidate: &CodeUnit,
4990        target: &CodeUnit,
4991    ) -> Option<CodeUnit> {
4992        let mut current = candidate.clone();
4993        let mut matched_target = same_visible_symbol(&current, target)
4994            || self.compatible_primary_template_redeclarations(&current, target);
4995        let mut seen = HashSet::default();
4996        loop {
4997            if !seen.insert(current.clone()) {
4998                return None;
4999            }
5000            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
5001                return matched_target
5002                    .then(|| target.clone())
5003                    .or_else(|| current.is_class().then_some(current));
5004            };
5005            if self.flattened_macro_namespace_alias_target_matches(
5006                analyzer,
5007                visible_from,
5008                &current,
5009                &alias_target,
5010                target,
5011            ) {
5012                return Some(target.clone());
5013            }
5014            if matches!(alias_target, StructuredAliasTarget::Builtin) {
5015                return matched_target
5016                    .then(|| target.clone())
5017                    .or_else(|| current.is_class().then_some(current));
5018            }
5019            // A non-template alias can name a template alias with explicit
5020            // arguments (for example, `using Result = Expected<int>`).  When
5021            // the requested target is that alias's primary declaration, keep
5022            // the primary identity before expanding the RHS arguments.  The
5023            // expansion would otherwise canonicalize through the underlying
5024            // implementation type and lose the target spelling used by the
5025            // forward resolver.
5026            if !self.cpp_template_metadata.contains_key(&current)
5027                && let Some(primary) =
5028                    self.resolve_structured_alias_primary(visible_from, &current, &alias_target)
5029                && (same_visible_symbol(&primary, target)
5030                    || self.compatible_primary_template_redeclarations(&primary, target))
5031            {
5032                return Some(target.clone());
5033            }
5034            if same_visible_symbol(&current, target) {
5035                return Some(target.clone());
5036            }
5037            if self.cpp_template_metadata.contains_key(&current) {
5038                return None;
5039            }
5040            let Some(next) =
5041                self.resolve_structured_alias_target(visible_from, &current, &alias_target)
5042            else {
5043                return matched_target.then(|| target.clone());
5044            };
5045            current = next;
5046            matched_target |= same_visible_symbol(&current, target)
5047                || self.compatible_primary_template_redeclarations(&current, target);
5048        }
5049    }
5050
5051    fn compatible_primary_template_redeclarations(
5052        &self,
5053        left: &CodeUnit,
5054        right: &CodeUnit,
5055    ) -> bool {
5056        let (Some(left_metadata), Some(right_metadata)) = (
5057            self.cpp_template_metadata.get(left),
5058            self.cpp_template_metadata.get(right),
5059        ) else {
5060            return false;
5061        };
5062        left_metadata.primary_fq_name == right_metadata.primary_fq_name
5063            && left_metadata.specialization_arguments.is_empty()
5064            && right_metadata.specialization_arguments.is_empty()
5065            && cpp_reconcile_primary_template_parameters(
5066                &[(left, left_metadata), (right, right_metadata)],
5067                right,
5068            )
5069            .is_some()
5070    }
5071
5072    fn alias_candidate_may_preserve_target(
5073        &self,
5074        analyzer: &CppGraphSource<'_>,
5075        visible_from: &ProjectFile,
5076        candidate: &CodeUnit,
5077        target: &CodeUnit,
5078    ) -> bool {
5079        let mut current = candidate.clone();
5080        let mut seen = HashSet::default();
5081        loop {
5082            if same_visible_symbol(&current, target)
5083                || self.compatible_primary_template_redeclarations(&current, target)
5084            {
5085                return true;
5086            }
5087            if self.cpp_template_metadata.contains_key(&current) {
5088                return true;
5089            }
5090            let Some(alias_target) = self.structured_alias_target(analyzer, &current) else {
5091                return false;
5092            };
5093            let StructuredAliasTarget::Named {
5094                components,
5095                global,
5096                arguments,
5097            } = alias_target
5098            else {
5099                return false;
5100            };
5101            if arguments.is_some() || !seen.insert(current.clone()) {
5102                return true;
5103            }
5104            let qualified = components.join("::");
5105            let next = if global {
5106                unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
5107            } else {
5108                self.resolve_unique_type_for_declaration(visible_from, &current, &qualified)
5109            };
5110            let Some(next) = next else {
5111                return true;
5112            };
5113            current = next;
5114        }
5115    }
5116
5117    /// Every indexed type declaration `raw_name` names when it is written in
5118    /// `declaration`'s namespace: the innermost enclosing namespace that holds
5119    /// the name wins, otherwise the name is looked up unqualified.
5120    fn type_candidates_for_declaration<'b>(
5121        &'b self,
5122        visible_from: &ProjectFile,
5123        declaration: &CodeUnit,
5124        raw_name: &str,
5125    ) -> Vec<&'b CodeUnit> {
5126        let Some(normalized) = normalize_reference_name(raw_name) else {
5127            return Vec::new();
5128        };
5129        if let Some(namespace) = cpp_namespace_for(declaration) {
5130            for prefix in namespace_prefixes(&namespace) {
5131                let qualified = format!("{prefix}::{normalized}");
5132                let candidates = self.type_candidates(visible_from, &qualified);
5133                if !candidates.is_empty() {
5134                    return candidates;
5135                }
5136            }
5137        }
5138        self.type_candidates(visible_from, &normalized)
5139    }
5140
5141    fn resolve_unique_type_for_declaration(
5142        &self,
5143        visible_from: &ProjectFile,
5144        declaration: &CodeUnit,
5145        raw_name: &str,
5146    ) -> Option<CodeUnit> {
5147        unique_logical_type_candidate(self.type_candidates_for_declaration(
5148            visible_from,
5149            declaration,
5150            raw_name,
5151        ))
5152    }
5153
5154    pub fn resolves_to_type(
5155        &self,
5156        analyzer: &CppGraphSource<'_>,
5157        file: &ProjectFile,
5158        raw_name: &str,
5159        target: &CodeUnit,
5160    ) -> bool {
5161        let Some(normalized) = normalize_reference_name(raw_name) else {
5162            return false;
5163        };
5164        let candidates = self.type_candidates(file, &normalized);
5165        if candidates.is_empty() {
5166            return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
5167        }
5168        let Some(resolved) =
5169            self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
5170        else {
5171            return false;
5172        };
5173        same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
5174    }
5175
5176    pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
5177        let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
5178        let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
5179        match resolved.kind() {
5180            CodeUnitType::Class => Some(resolved),
5181            _ if is_type_alias(&resolved) => self.alias_target(&resolved),
5182            _ => None,
5183        }
5184    }
5185
5186    pub fn canonical_type_for_reference(
5187        &self,
5188        file: &ProjectFile,
5189        raw_name: &str,
5190    ) -> Option<CodeUnit> {
5191        let resolved = self.resolve_type(file, raw_name)?;
5192        self.alias_target(&resolved).or(Some(resolved))
5193    }
5194
5195    pub fn parser_alias_resolves_to_type(
5196        &self,
5197        analyzer: &CppGraphSource<'_>,
5198        file: &ProjectFile,
5199        raw_name: &str,
5200        target: &CodeUnit,
5201    ) -> bool {
5202        let Some(alias_name) = normalize_reference_name(raw_name) else {
5203            return false;
5204        };
5205        let Some(cpp) = analyzer.cpp else {
5206            return false;
5207        };
5208        let matches_file = |source_file: &ProjectFile| {
5209            self.file_alias_matches(cpp, source_file, &alias_name, target)
5210        };
5211        self.visible_source_files_by_root.get(file).map_or_else(
5212            || matches_file(file),
5213            |files| files.iter().any(matches_file),
5214        )
5215    }
5216
5217    fn file_alias_matches(
5218        &self,
5219        cpp: &dyn CppSource,
5220        file: &ProjectFile,
5221        alias_name: &str,
5222        target: &CodeUnit,
5223    ) -> bool {
5224        let cell = {
5225            let mut cells = self.alias_cells.lock().expect("alias cell map lock");
5226            Arc::clone(
5227                cells
5228                    .entry(file.clone())
5229                    .or_insert_with(|| Arc::new(OnceLock::new())),
5230            )
5231        };
5232        cell.get_or_init(|| {
5233            #[cfg(any(test, feature = "test-support"))]
5234            {
5235                *self
5236                    .alias_source_parse_counts
5237                    .lock()
5238                    .expect("alias source parse count lock")
5239                    .entry(file.clone())
5240                    .or_default() += 1;
5241            }
5242            aliases_from_prepared_source(cpp, file).into_boxed_slice()
5243        })
5244        .iter()
5245        .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
5246    }
5247
5248    #[cfg(any(test, feature = "test-support"))]
5249    pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
5250        self.visible_source_files_by_root
5251            .get(file)
5252            .cloned()
5253            .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
5254    }
5255
5256    #[cfg(any(test, feature = "test-support"))]
5257    pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
5258        self.alias_source_parse_counts
5259            .lock()
5260            .expect("alias source parse count lock")
5261            .get(file)
5262            .copied()
5263            .unwrap_or(0)
5264    }
5265
5266    pub fn resolve_named(
5267        &self,
5268        file: &ProjectFile,
5269        raw_name: &str,
5270        kind: TargetKind,
5271    ) -> Option<CodeUnit> {
5272        let normalized = normalize_reference_name(raw_name)?;
5273        self.named_candidates_for_normalized(file, &normalized, kind)
5274            .into_iter()
5275            .next()
5276            .cloned()
5277    }
5278
5279    pub fn contains_named_symbol(
5280        &self,
5281        file: &ProjectFile,
5282        raw_name: &str,
5283        kind: TargetKind,
5284        target: &CodeUnit,
5285    ) -> bool {
5286        let Some(normalized) = normalize_reference_name(raw_name) else {
5287            return false;
5288        };
5289        self.named_candidates_for_normalized(file, &normalized, kind)
5290            .into_iter()
5291            .any(|unit| {
5292                matches_kind_for_lookup(unit, kind)
5293                    && reference_matches_unit(&normalized, unit)
5294                    && same_visible_symbol(unit, target)
5295            })
5296    }
5297
5298    pub fn named_candidates(
5299        &self,
5300        file: &ProjectFile,
5301        raw_name: &str,
5302        kind: TargetKind,
5303    ) -> Vec<CodeUnit> {
5304        let Some(normalized) = normalize_reference_name(raw_name) else {
5305            return Vec::new();
5306        };
5307        self.named_candidates_for_normalized(file, &normalized, kind)
5308            .into_iter()
5309            .cloned()
5310            .collect()
5311    }
5312
5313    pub fn resolve_known_non_target(
5314        &self,
5315        file: &ProjectFile,
5316        raw_name: &str,
5317        kind: TargetKind,
5318        target: &CodeUnit,
5319    ) -> bool {
5320        let Some(normalized) = normalize_reference_name(raw_name) else {
5321            return false;
5322        };
5323        normalized.contains("::")
5324            && self
5325                .named_candidates_for_normalized(file, &normalized, kind)
5326                .into_iter()
5327                .any(|unit| {
5328                    matches_kind_for_lookup(unit, kind)
5329                        && reference_matches_unit(&normalized, unit)
5330                        && !same_visible_symbol(unit, target)
5331                })
5332    }
5333
5334    pub fn resolve_call_return_binding(
5335        &self,
5336        analyzer: &CppGraphSource<'_>,
5337        file: &ProjectFile,
5338        raw_name: &str,
5339        arity: usize,
5340        lexical_namespace: Option<&str>,
5341        direct_type: Option<&CodeUnit>,
5342    ) -> Option<CppScanBinding> {
5343        let normalized = normalize_reference_name(raw_name)?;
5344        let mut candidates = Vec::new();
5345        for function in
5346            self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
5347        {
5348            if cpp_callable_arity(analyzer, function).accepts(arity)
5349                && !direct_type.is_some_and(|direct_type| {
5350                    self.callable_is_constructor_declaration(analyzer, function)
5351                        && type_owner_of(analyzer, function)
5352                            .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
5353                })
5354            {
5355                candidates.push(function.clone());
5356            }
5357        }
5358        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
5359        unanimous_return_binding(analyzer, self, file, &candidates)
5360    }
5361
5362    pub fn resolve_call_return_binding_without_arity(
5363        &self,
5364        analyzer: &CppGraphSource<'_>,
5365        file: &ProjectFile,
5366        raw_name: &str,
5367        lexical_namespace: Option<&str>,
5368        direct_type: Option<&CodeUnit>,
5369    ) -> (bool, Option<CppScanBinding>) {
5370        let Some(normalized) = normalize_reference_name(raw_name) else {
5371            return (false, None);
5372        };
5373        let mut candidates = self
5374            .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
5375            .into_iter()
5376            .filter(|function| {
5377                function.is_function()
5378                    && !direct_type.is_some_and(|direct_type| {
5379                        self.callable_is_constructor_declaration(analyzer, function)
5380                            && type_owner_of(analyzer, function)
5381                                .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
5382                    })
5383            })
5384            .cloned()
5385            .collect::<Vec<_>>();
5386        candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
5387        let has_candidates = !candidates.is_empty();
5388        (
5389            has_candidates,
5390            unanimous_return_binding(analyzer, self, file, &candidates),
5391        )
5392    }
5393
5394    pub fn visible_identifier_candidates<'b>(
5395        &'b self,
5396        file: &ProjectFile,
5397        identifier: &str,
5398    ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
5399        self.visible_by_identifier
5400            .get(file)
5401            .and_then(|by_name| by_name.get(identifier))
5402            .into_iter()
5403            .flatten()
5404    }
5405
5406    /// Return terminal reference names that can denote `target` from `file`.
5407    ///
5408    /// The indexed candidate table covers ordinary declarations and aliases;
5409    /// parser-only aliases are read through their per-file cells so this path
5410    /// never reparses a source that has already been inspected by the visibility
5411    /// index.
5412    pub fn visible_type_reference_component_names_for_target(
5413        &self,
5414        analyzer: &CppGraphSource<'_>,
5415        file: &ProjectFile,
5416        target: &CodeUnit,
5417    ) -> HashSet<String> {
5418        let mut names = HashSet::from_iter([target.identifier().to_string()]);
5419        if let Some(metadata) = self.cpp_template_metadata.get(target) {
5420            names.insert(metadata.primary_name.clone());
5421        }
5422
5423        if let Some(by_identifier) = self.visible_by_identifier.get(file) {
5424            for (identifier, candidates) in by_identifier {
5425                if candidates.iter().any(|candidate| {
5426                    (candidate.is_class()
5427                        && (same_visible_symbol(candidate, target)
5428                            || self.compatible_primary_template_redeclarations(candidate, target)))
5429                        || (declared_type_alias(analyzer, candidate)
5430                            && self.alias_candidate_may_preserve_target(
5431                                analyzer, file, candidate, target,
5432                            ))
5433                }) {
5434                    names.insert(identifier.clone());
5435                }
5436            }
5437        }
5438
5439        names.extend(self.visible_parser_alias_names_for_target(file, target));
5440
5441        names
5442    }
5443
5444    pub fn indexed_structural_class_scope(
5445        &self,
5446        file: &ProjectFile,
5447        class: Node<'_>,
5448        source: &str,
5449    ) -> Option<Vec<String>> {
5450        let key = (file.clone(), class.start_byte(), class.end_byte());
5451        if let Some(cached) = self
5452            .indexed_structural_class_scopes
5453            .lock()
5454            .expect("C++ indexed structural-class scope cache poisoned")
5455            .get(&key)
5456            .cloned()
5457        {
5458            return cached;
5459        }
5460        let resolved = (|| {
5461            let name = class.child_by_field_name("name")?;
5462            let identifier = if name.kind() == "template_type" {
5463                node_text(name.child_by_field_name("name")?, source).to_string()
5464            } else {
5465                let mut components = Vec::new();
5466                append_cpp_name_components(name, source, &mut components)?;
5467                components.last()?.clone()
5468            };
5469            let visible = self
5470                .visible_identifier_candidates(file, &identifier)
5471                .cloned()
5472                .collect::<Vec<_>>();
5473            let mut visible = visible;
5474            for candidate in
5475                self.visible_by_file
5476                    .get(file)
5477                    .into_iter()
5478                    .flatten()
5479                    .filter(|candidate| {
5480                        self.cpp_template_metadata
5481                            .get(candidate)
5482                            .is_some_and(|metadata| metadata.primary_name == identifier)
5483                    })
5484            {
5485                if !visible
5486                    .iter()
5487                    .any(|existing| same_logical_symbol(existing, candidate))
5488                {
5489                    visible.push(candidate.clone());
5490                }
5491            }
5492            // Built once per call rather than per candidate; `cpp_source` rebuilds
5493            // the five-field source from the same `self.cpp` on every call.
5494            let cpp_source = self.cpp_source();
5495            let candidates = visible
5496                .iter()
5497                .filter(|candidate| {
5498                    candidate.source() == file
5499                        && candidate.is_class()
5500                        && !declared_type_alias(&cpp_source, candidate)
5501                        && self.cpp.ranges(candidate).iter().any(|range| {
5502                            range.start_byte <= class.start_byte()
5503                                && class.end_byte() <= range.end_byte
5504                        })
5505                })
5506                .collect::<Vec<_>>();
5507            let owner = if name.kind() == "template_type" {
5508                let expected = normalize_cpp_whitespace(node_text(name, source));
5509                let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
5510                let exact = candidates
5511                    .iter()
5512                    .copied()
5513                    .filter(|candidate| {
5514                        candidate
5515                            .fq()
5516                            .segments()
5517                            .iter()
5518                            .rev()
5519                            .find_map(|&segment| {
5520                                let (text, kind) = interner.resolve(segment);
5521                                matches!(
5522                                    kind,
5523                                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
5524                                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
5525                                )
5526                                .then_some(text)
5527                            })
5528                            .is_some_and(|text| text == expected)
5529                    })
5530                    .collect::<Vec<_>>();
5531                unique_logical_type_candidate(exact)
5532                    .or_else(|| unique_logical_type_candidate(candidates.clone()))?
5533            } else {
5534                unique_logical_type_candidate(candidates)?
5535            };
5536            Some(canonical_cpp_scope_components(&owner))
5537        })();
5538        self.indexed_structural_class_scopes
5539            .lock()
5540            .expect("C++ indexed structural-class scope cache poisoned")
5541            .insert(key, resolved.clone());
5542        resolved
5543    }
5544
5545    pub fn indexed_enclosing_owner_scope(
5546        &self,
5547        analyzer: &CppGraphSource<'_>,
5548        file: &ProjectFile,
5549        node: Node<'_>,
5550    ) -> Option<Vec<String>> {
5551        let anchor = std::iter::successors(Some(node), |current| current.parent())
5552            .find(|current| {
5553                matches!(
5554                    current.kind(),
5555                    "function_definition"
5556                        | "class_specifier"
5557                        | "struct_specifier"
5558                        | "union_specifier"
5559                )
5560            })
5561            .unwrap_or(node);
5562        let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
5563        if let Some(cached) = self
5564            .indexed_enclosing_owner_scopes
5565            .lock()
5566            .expect("C++ indexed enclosing-owner scope cache poisoned")
5567            .get(&key)
5568            .cloned()
5569        {
5570            return cached;
5571        }
5572        let resolved = (|| {
5573            let range = Range {
5574                start_byte: node.start_byte(),
5575                end_byte: node.end_byte(),
5576                start_line: node.start_position().row,
5577                end_line: node.end_position().row,
5578            };
5579            let start = analyzer.enclosing_code_unit(file, &range)?;
5580            let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
5581                start,
5582                |unit| self.cached_precise_parent_of(analyzer, unit),
5583            )
5584            .find(|unit| {
5585                unit.is_class()
5586                    && !analyzer
5587                        .type_alias_provider()
5588                        .is_some_and(|provider| provider.is_type_alias(unit))
5589            })?;
5590            Some(canonical_cpp_scope_components(&owner))
5591        })();
5592        self.indexed_enclosing_owner_scopes
5593            .lock()
5594            .expect("C++ indexed enclosing-owner scope cache poisoned")
5595            .insert(key, resolved.clone());
5596        resolved
5597    }
5598
5599    fn cached_precise_parent_of(
5600        &self,
5601        analyzer: &CppGraphSource<'_>,
5602        code_unit: &CodeUnit,
5603    ) -> Option<CodeUnit> {
5604        if let Some(cached) = self
5605            .precise_parent_cache
5606            .lock()
5607            .expect("C++ precise-parent cache poisoned")
5608            .get(code_unit)
5609            .cloned()
5610        {
5611            return cached;
5612        }
5613        let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
5614        self.precise_parent_cache
5615            .lock()
5616            .expect("C++ precise-parent cache poisoned")
5617            .insert(code_unit.clone(), resolved.clone());
5618        resolved
5619    }
5620
5621    pub fn callable_is_constructor_declaration(
5622        &self,
5623        analyzer: &CppGraphSource<'_>,
5624        candidate: &CodeUnit,
5625    ) -> bool {
5626        if !candidate.is_function() {
5627            return false;
5628        }
5629        let Some(prepared) = self.cpp.prepared_syntax(candidate.source()) else {
5630            return false;
5631        };
5632        let root = prepared.tree().root_node();
5633        let candidate_ranges = analyzer.ranges(candidate);
5634        let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
5635            let mut current = root
5636                .descendant_for_byte_range(range.start_byte, range.end_byte)
5637                .and_then(|node| node.parent());
5638            while let Some(node) = current {
5639                if matches!(
5640                    node.kind(),
5641                    "class_specifier" | "struct_specifier" | "union_specifier"
5642                ) {
5643                    return node
5644                        .child_by_field_name("name")
5645                        .map(|name| terminal_name(node_text(name, prepared.source())))
5646                        .is_some_and(|name| name == candidate.identifier());
5647                }
5648                current = node.parent();
5649            }
5650            false
5651        });
5652        if enclosed_by_matching_type {
5653            return true;
5654        }
5655        let indexed_containment = analyzer
5656            .declarations(candidate.source())
5657            .into_iter()
5658            .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
5659            .any(|owner| {
5660                analyzer.ranges(&owner).iter().any(|owner_range| {
5661                    candidate_ranges.iter().any(|candidate_range| {
5662                        owner_range.start_byte <= candidate_range.start_byte
5663                            && candidate_range.end_byte <= owner_range.end_byte
5664                    })
5665                })
5666            });
5667        if indexed_containment {
5668            return true;
5669        }
5670        let metadata = analyzer.signature_metadata(candidate);
5671        !metadata.is_empty()
5672            && metadata
5673                .iter()
5674                .all(|signature| signature.return_type_text().is_none())
5675    }
5676
5677    pub fn type_name_candidates<'b>(
5678        &'b self,
5679        file: &ProjectFile,
5680        normalized: &str,
5681    ) -> Vec<&'b CodeUnit> {
5682        self.candidate_units(file, normalized, TargetKind::Type)
5683    }
5684
5685    pub fn visible_members_for_owner_name<'b>(
5686        &'b self,
5687        file: &ProjectFile,
5688        owner: &CodeUnit,
5689        name: &str,
5690    ) -> Vec<&'b CodeUnit> {
5691        self.visible_identifier_candidates(file, name)
5692            .filter(|unit| {
5693                // Structured owner pop on the unit's own `fq()` (shared with
5694                // `CodeUnitIndex::parent_of`), not a re-split of its rendered fqn
5695                // string.
5696                brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
5697                    .is_some_and(|parent| parent == owner.fq_name())
5698            })
5699            .collect()
5700    }
5701
5702    pub fn visible_member_for_owner_name(
5703        &self,
5704        file: &ProjectFile,
5705        owner: &CodeUnit,
5706        name: &str,
5707    ) -> VisibleMemberResolution {
5708        let candidates = self.visible_members_for_owner_name(file, owner, name);
5709        let mut callables = Vec::new();
5710        let mut non_callable = None;
5711        for candidate in candidates {
5712            if candidate.is_function() {
5713                callables.push(candidate.clone());
5714            } else if non_callable.is_none() {
5715                non_callable = Some(candidate.clone());
5716            }
5717        }
5718        match (callables.is_empty(), non_callable) {
5719            (false, None) => VisibleMemberResolution::Callable(callables),
5720            (true, Some(_)) => VisibleMemberResolution::NonCallable,
5721            (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
5722            (true, None) => VisibleMemberResolution::Missing,
5723        }
5724    }
5725
5726    fn field_declared_type_fact(
5727        &self,
5728        analyzer: &CppGraphSource<'_>,
5729        field: &CodeUnit,
5730    ) -> Option<DeclaredFieldTypeFact> {
5731        if let Some(cached) = self
5732            .field_type_facts
5733            .lock()
5734            .expect("C++ field type fact cache poisoned")
5735            .get(field)
5736            .cloned()
5737        {
5738            return cached;
5739        }
5740        let decoded = decode_field_declared_type_fact(analyzer, field);
5741        self.field_type_facts
5742            .lock()
5743            .expect("C++ field type fact cache poisoned")
5744            .insert(field.clone(), decoded.clone());
5745        decoded
5746    }
5747
5748    fn structured_alias_target(
5749        &self,
5750        analyzer: &CppGraphSource<'_>,
5751        unit: &CodeUnit,
5752    ) -> Option<StructuredAliasTarget> {
5753        if let Some(cached) = self
5754            .structured_alias_targets
5755            .lock()
5756            .expect("C++ structured alias target cache poisoned")
5757            .get(unit)
5758            .cloned()
5759        {
5760            return cached;
5761        }
5762        let decoded = decode_structured_alias_target(analyzer, unit);
5763        self.structured_alias_targets
5764            .lock()
5765            .expect("C++ structured alias target cache poisoned")
5766            .insert(unit.clone(), decoded.clone());
5767        decoded
5768    }
5769
5770    pub fn type_candidates<'b>(
5771        &'b self,
5772        file: &ProjectFile,
5773        normalized: &str,
5774    ) -> Vec<&'b CodeUnit> {
5775        let mut candidates = self
5776            .candidate_units(file, normalized, TargetKind::Type)
5777            .into_iter()
5778            .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
5779            .collect::<Vec<_>>();
5780        dedup_unit_refs(&mut candidates);
5781        candidates
5782    }
5783
5784    pub fn named_candidates_for_normalized<'b>(
5785        &'b self,
5786        file: &ProjectFile,
5787        normalized: &str,
5788        kind: TargetKind,
5789    ) -> Vec<&'b CodeUnit> {
5790        let mut candidates = self
5791            .candidate_units(file, normalized, kind)
5792            .into_iter()
5793            .filter(|unit| {
5794                matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
5795            })
5796            .collect::<Vec<_>>();
5797        dedup_unit_refs(&mut candidates);
5798        candidates
5799    }
5800
5801    pub fn candidate_units<'b>(
5802        &'b self,
5803        file: &ProjectFile,
5804        normalized: &str,
5805        kind: TargetKind,
5806    ) -> Vec<&'b CodeUnit> {
5807        if normalized.contains("::") {
5808            // `normalized` comes from `normalize_cpp_reference_text`, which
5809            // truncates at the first `(`/`{`/`<`, leaving a plain `::`-joined
5810            // qualified-id with no embedded `.`/`/`/`\` and operator tokens
5811            // kept intact by the shared splitter's operator merge — the same
5812            // domain `cpp_reference_fqn_candidates` below already parses with
5813            // the shared splitter. Re-tokenizing and taking the last segment
5814            // reproduces `rsplit("::").find(non-empty)`'s terminal-component
5815            // scan exactly.
5816            let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5817                brokk_bifrost_core::analyzer::Language::Cpp,
5818                normalized,
5819            )
5820            .pop() else {
5821                return Vec::new();
5822            };
5823            let fqns = cpp_reference_fqn_candidates(normalized, kind);
5824            return self
5825                .visible_identifier_candidates(file, &identifier)
5826                .filter(|unit| {
5827                    #[cfg(any(test, feature = "test-support"))]
5828                    self.qualified_candidate_inspections
5829                        .fetch_add(1, Ordering::Relaxed);
5830                    fqns.iter().any(|fqn| unit.fq_name() == *fqn)
5831                        || canonical_cpp_name_matches(unit, normalized)
5832                })
5833                .collect();
5834        }
5835        self.visible_identifier_candidates(file, normalized)
5836            .collect()
5837    }
5838
5839    #[cfg(any(test, feature = "test-support"))]
5840    pub fn reset_qualified_candidate_inspections(&self) {
5841        self.qualified_candidate_inspections
5842            .store(0, Ordering::Relaxed);
5843    }
5844
5845    #[cfg(any(test, feature = "test-support"))]
5846    pub fn qualified_candidate_inspections(&self) -> usize {
5847        self.qualified_candidate_inspections.load(Ordering::Relaxed)
5848    }
5849
5850    #[cfg(any(test, feature = "test-support"))]
5851    pub fn reset_target_preserving_type_resolution_count(&self) {
5852        self.target_preserving_type_resolution_count
5853            .store(0, Ordering::Relaxed);
5854    }
5855
5856    #[cfg(any(test, feature = "test-support"))]
5857    pub fn target_preserving_type_resolution_count(&self) -> usize {
5858        self.target_preserving_type_resolution_count
5859            .load(Ordering::Relaxed)
5860    }
5861
5862    #[cfg(any(test, feature = "test-support"))]
5863    pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
5864        self.visible_parser_alias_name_set_build_count
5865            .load(Ordering::Relaxed)
5866    }
5867
5868    #[cfg(any(test, feature = "test-support"))]
5869    pub fn visible_parser_alias_target_names_build_count(&self) -> usize {
5870        self.visible_parser_alias_target_names_build_count
5871            .load(Ordering::Relaxed)
5872    }
5873}
5874
5875#[derive(Default)]
5876struct IncludeGraph {
5877    targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
5878}
5879
5880impl IncludeGraph {
5881    fn extend_with<F>(
5882        &mut self,
5883        root: &ProjectFile,
5884        cancellation: Option<&CancellationToken>,
5885        targets_for: &mut F,
5886    ) where
5887        F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
5888    {
5889        let mut stack = vec![root.clone()];
5890        while let Some(file) = stack.pop() {
5891            if cancellation.is_some_and(CancellationToken::is_cancelled) {
5892                break;
5893            }
5894            if self.targets_by_file.contains_key(&file) {
5895                continue;
5896            }
5897            let targets = targets_for(&file);
5898            stack.extend(targets.iter().cloned());
5899            self.targets_by_file.insert(file, targets);
5900        }
5901    }
5902
5903    fn files(&self) -> impl Iterator<Item = &ProjectFile> {
5904        self.targets_by_file.keys()
5905    }
5906
5907    fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
5908        self.targets_by_file
5909            .get(file)
5910            .map(Vec::as_slice)
5911            .unwrap_or_default()
5912    }
5913}
5914
5915pub struct VisibilityData {
5916    pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
5917    pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
5918}
5919
5920pub fn build_visibility_data<F, D>(
5921    roots: &HashSet<ProjectFile>,
5922    cancellation: Option<&CancellationToken>,
5923    mut targets_for: F,
5924    mut declarations_for: D,
5925) -> VisibilityData
5926where
5927    F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
5928    D: FnMut(&ProjectFile) -> BTreeSet<CodeUnit>,
5929{
5930    let mut include_graph = IncludeGraph::default();
5931    for file in roots {
5932        if cancellation.is_some_and(CancellationToken::is_cancelled) {
5933            break;
5934        }
5935        include_graph.extend_with(file, cancellation, &mut targets_for);
5936    }
5937    let declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
5938        .files()
5939        .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
5940        .map(|file| (file.clone(), declarations_for(file)))
5941        .collect();
5942    let mut visible_by_file = HashMap::default();
5943    let mut visible_source_files_by_root = HashMap::default();
5944    for file in roots {
5945        if cancellation.is_some_and(CancellationToken::is_cancelled) {
5946            break;
5947        }
5948        let mut visited = HashSet::default();
5949        let mut visible = HashSet::default();
5950        collect_visible_declarations(
5951            &include_graph,
5952            &declarations_by_file,
5953            file,
5954            &mut visited,
5955            &mut visible,
5956            cancellation,
5957        );
5958        visible_by_file.insert(file.clone(), visible);
5959        visible_source_files_by_root.insert(file.clone(), visited);
5960    }
5961    VisibilityData {
5962        visible_by_file,
5963        visible_source_files_by_root,
5964    }
5965}
5966
5967/// Admit the class that an out-of-line definition proves is in scope.
5968///
5969/// `Owner::member(...) { ... }` in a file is structured proof that `Owner`
5970/// names a class-like entity in that file's scope: a member declaration can
5971/// live in a file other than its class's only when it is written out of line.
5972/// A file a build concatenates rather than compiles carries no `#include` edge
5973/// to the header declaring `Owner` -- google/wuffs
5974/// `internal/cgen/auxiliary/image.cc` defines
5975/// `DecodeImageResult::DecodeImageResult` and never includes `image.hh` -- so
5976/// every unqualified member and constructor reference in it had no candidate at
5977/// all (#1832).
5978///
5979/// The evidence is the indexed declaration's own owner name, taken from its
5980/// `FqName`, so this stays a structured answer rather than a text fallback.
5981/// Only an owner the file cannot already see is admitted: that is what keeps a
5982/// header declaring its own class from additionally seeing every same-named
5983/// class in the workspace, and it makes the pass free for the ordinary file
5984/// whose owners are all visible.
5985fn extend_with_out_of_line_owner_bindings(
5986    cpp: &dyn CppSource,
5987    visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
5988) {
5989    for (file, visible) in visible_by_file.iter_mut() {
5990        // The include-closure walk seeds every root with its own declarations,
5991        // so the file's members are already here; re-reading them from the
5992        // analyzer would pay for the same declaration set twice.
5993        let mut unseen_owners: HashSet<String> = visible
5994            .iter()
5995            .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
5996            .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
5997            .collect();
5998        if unseen_owners.is_empty() {
5999            continue;
6000        }
6001        for unit in visible.iter().filter(|unit| unit.is_class()) {
6002            unseen_owners.remove(&unit.fq_name());
6003        }
6004        let admitted = unseen_owners
6005            .iter()
6006            .flat_map(|owner| cpp.definitions(owner))
6007            .filter(CodeUnit::is_class)
6008            .collect::<Vec<_>>();
6009        visible.extend(admitted);
6010    }
6011}
6012
6013pub enum VisibleMemberResolution {
6014    Callable(Vec<CodeUnit>),
6015    NonCallable,
6016    AmbiguousKind,
6017    Missing,
6018}
6019
6020#[derive(Clone)]
6021pub enum EnclosingMemberOwnerResolution {
6022    Owner(CodeUnit),
6023    Ambiguous,
6024    Missing,
6025}
6026
6027pub fn resolve_declaring_member_owner(
6028    analyzer: &CppGraphSource<'_>,
6029    visibility: &VisibilityIndex<'_>,
6030    file: &ProjectFile,
6031    receiver_owner: &CodeUnit,
6032    member_name: &str,
6033) -> EnclosingMemberOwnerResolution {
6034    let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6035        return EnclosingMemberOwnerResolution::Missing;
6036    };
6037    let Some(receiver_owner) =
6038        visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
6039    else {
6040        return EnclosingMemberOwnerResolution::Ambiguous;
6041    };
6042    let resolve_level = |frontier: &[CodeUnit]| {
6043        let mut member_owners = Vec::new();
6044        for raw_owner in frontier {
6045            let Some(owner) =
6046                visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
6047            else {
6048                return EnclosingMemberOwnerResolution::Ambiguous;
6049            };
6050            for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
6051                let Some(member_owner) = type_owner_of(analyzer, member) else {
6052                    return EnclosingMemberOwnerResolution::Ambiguous;
6053                };
6054                if !member_owners
6055                    .iter()
6056                    .any(|existing| same_visible_symbol(existing, &member_owner))
6057                {
6058                    member_owners.push(member_owner);
6059                }
6060            }
6061        }
6062        match member_owners.len() {
6063            0 => EnclosingMemberOwnerResolution::Missing,
6064            1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
6065            _ => EnclosingMemberOwnerResolution::Ambiguous,
6066        }
6067    };
6068    // The first declaration on each structured base path hides deeper names,
6069    // regardless of whether its callable overload is applicable at a particular
6070    // call site. Applicability is checked only after this owner is established.
6071    let direct = resolve_level(std::slice::from_ref(&receiver_owner));
6072    if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
6073        return direct;
6074    }
6075    let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
6076    let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6077    let mut path_matches = Vec::new();
6078    while let Some(raw_owner) = stack.pop() {
6079        let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6080        else {
6081            return EnclosingMemberOwnerResolution::Ambiguous;
6082        };
6083        // Persisted hierarchy edges do not encode virtual-base or base-subobject paths.
6084        // Propagate at most two occurrences of each owner: that preserves the distinction
6085        // between one and multiple resolving base paths without exponential diamond walks.
6086        let propagated = propagated_counts.entry(owner.clone()).or_default();
6087        if *propagated == 2 {
6088            continue;
6089        }
6090        *propagated += 1;
6091        match resolve_level(std::slice::from_ref(&owner)) {
6092            EnclosingMemberOwnerResolution::Owner(owner) => {
6093                path_matches.push(owner);
6094                if path_matches.len() == 2 {
6095                    return EnclosingMemberOwnerResolution::Ambiguous;
6096                }
6097            }
6098            EnclosingMemberOwnerResolution::Ambiguous => {
6099                return EnclosingMemberOwnerResolution::Ambiguous;
6100            }
6101            EnclosingMemberOwnerResolution::Missing => {
6102                stack.extend(hierarchy.get_direct_ancestors(&owner));
6103            }
6104        }
6105    }
6106    match path_matches.len() {
6107        0 => EnclosingMemberOwnerResolution::Missing,
6108        1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
6109        _ => unreachable!("base-path matches are capped at one before returning"),
6110    }
6111}
6112
6113pub fn lexical_component_tiers<'a>(
6114    components: &'a [String],
6115    global: bool,
6116    lexical_scope: &'a [String],
6117) -> impl Iterator<Item = Vec<String>> + 'a {
6118    let first_prefix_len = if global { 0 } else { lexical_scope.len() };
6119    (0..=first_prefix_len).rev().map(move |prefix_len| {
6120        let mut qualified = Vec::with_capacity(prefix_len + components.len());
6121        qualified.extend_from_slice(&lexical_scope[..prefix_len]);
6122        qualified.extend_from_slice(components);
6123        qualified
6124    })
6125}
6126
6127pub fn build_visible_identifier_index(
6128    analyzer: &CppGraphSource<'_>,
6129    visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
6130    visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
6131    global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
6132) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
6133    let mut out = HashMap::default();
6134    for (file, visible) in visible_by_file {
6135        let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
6136        for unit in visible {
6137            if unit.is_field()
6138                && !visible_source_files_by_root
6139                    .get(file)
6140                    .is_some_and(|sources| sources.contains(unit.source()))
6141                && cpp_global_field_has_internal_linkage_cached(
6142                    analyzer,
6143                    global_field_internal_linkage,
6144                    unit,
6145                )
6146            {
6147                continue;
6148            }
6149            by_identifier
6150                .entry(unit.identifier().to_string())
6151                .or_default()
6152                .push(unit.clone());
6153        }
6154        for units in by_identifier.values_mut() {
6155            sort_lookup_units(units);
6156            units.dedup();
6157        }
6158        out.insert(file.clone(), by_identifier);
6159    }
6160    out
6161}
6162
6163fn sort_lookup_units(units: &mut [CodeUnit]) {
6164    units.sort_by(|left, right| {
6165        left.fq_name()
6166            .cmp(&right.fq_name())
6167            .then_with(|| left.signature().cmp(&right.signature()))
6168            .then_with(|| left.source().cmp(right.source()))
6169            .then_with(|| left.kind().cmp(&right.kind()))
6170            .then_with(|| {
6171                left.package_segment_count()
6172                    .cmp(&right.package_segment_count())
6173            })
6174            .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
6175            .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
6176    });
6177}
6178
6179fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
6180    let interner = segment_interner();
6181    for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
6182        let (left_text, left_kind) = interner.resolve(left_id);
6183        let (right_text, right_kind) = interner.resolve(right_id);
6184        let order = left_text
6185            .cmp(right_text)
6186            .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
6187        if order != CmpOrdering::Equal {
6188            return order;
6189        }
6190    }
6191    left.len().cmp(&right.len())
6192}
6193
6194const fn segment_kind_order(kind: SegmentKind) -> u8 {
6195    match kind {
6196        SegmentKind::Path => 0,
6197        SegmentKind::Package => 1,
6198        SegmentKind::Type => 2,
6199        SegmentKind::Companion => 3,
6200        SegmentKind::Nested => 4,
6201        SegmentKind::Member => 5,
6202        SegmentKind::Unknown => 6,
6203    }
6204}
6205
6206fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
6207    let mut deduped = Vec::with_capacity(units.len());
6208    for unit in units.drain(..) {
6209        if !deduped.contains(&unit) {
6210            deduped.push(unit);
6211        }
6212    }
6213    *units = deduped;
6214}
6215
6216pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
6217    // Same domain as `candidate_units` above: `reference` is a plain
6218    // `::`-joined qualified-id with operator tokens kept intact by the shared
6219    // splitter's operator merge.
6220    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6221        brokk_bifrost_core::analyzer::Language::Cpp,
6222        reference,
6223    );
6224    if parts.is_empty() {
6225        return Vec::new();
6226    }
6227
6228    let mut candidates = Vec::new();
6229    for package_len in 0..parts.len() {
6230        let package = parts[..package_len].join("::");
6231        let rest = &parts[package_len..];
6232        if rest.is_empty() {
6233            continue;
6234        }
6235        match kind {
6236            TargetKind::Type | TargetKind::Constructor => {
6237                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
6238                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
6239            }
6240            TargetKind::FreeFunction
6241            | TargetKind::Method
6242            | TargetKind::GlobalField
6243            | TargetKind::MemberField
6244            | TargetKind::Macro => {
6245                push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
6246                if rest.len() > 1 {
6247                    let owner = rest[..rest.len() - 1].join("$");
6248                    let short = format!("{}.{}", owner, rest[rest.len() - 1]);
6249                    push_cpp_fqn_candidate(&mut candidates, &package, &short);
6250                }
6251            }
6252        }
6253    }
6254    candidates
6255}
6256
6257fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
6258    let fqn = if package.is_empty() {
6259        short.to_string()
6260    } else {
6261        format!("{package}.{short}")
6262    };
6263    if !out.contains(&fqn) {
6264        out.push(fqn);
6265    }
6266}
6267
6268pub fn infer_cpp_initializer_type(
6269    analyzer: &CppGraphSource<'_>,
6270    visibility: &VisibilityIndex<'_>,
6271    file: &ProjectFile,
6272    source: &str,
6273    node: Node<'_>,
6274) -> Option<CodeUnit> {
6275    infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
6276        .and_then(|binding| binding.unit)
6277}
6278
6279pub fn infer_cpp_initializer_binding(
6280    analyzer: &CppGraphSource<'_>,
6281    visibility: &VisibilityIndex<'_>,
6282    file: &ProjectFile,
6283    source: &str,
6284    node: Node<'_>,
6285    receiver_resolver: Option<&ReceiverResolver<'_>>,
6286) -> Option<CppScanBinding> {
6287    match node.kind() {
6288        "new_expression" => {
6289            let text = normalize_cpp_whitespace(node_text(node, source));
6290            let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
6291            let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
6292            let name = normalize_cpp_type_name(type_text);
6293            Some(CppScanBinding::from_type_name(
6294                name.clone(),
6295                visibility.resolve_type(file, &name),
6296                1,
6297            ))
6298        }
6299        "call_expression" => node.child_by_field_name("function").and_then(|function| {
6300            let function_text = node_text(function, source);
6301            let direct_type_binding = visibility
6302                .resolve_type(file, function_text)
6303                .map(|unit| CppScanBinding::from_unit(unit, 0));
6304            if function.kind() == "template_function" && direct_type_binding.is_some() {
6305                let lexical_namespace = enclosing_namespace_context(node, source);
6306                let arity = visibility.call_arity_evidence(file, node, source).exact();
6307                if let Some(arity) = arity
6308                    && let Some(binding) = visibility.resolve_call_return_binding(
6309                        analyzer,
6310                        file,
6311                        function_text,
6312                        arity,
6313                        lexical_namespace.as_deref(),
6314                        direct_type_binding
6315                            .as_ref()
6316                            .and_then(|binding| binding.unit.as_ref()),
6317                    )
6318                {
6319                    return Some(binding);
6320                }
6321                let (has_callable, callable_binding) = visibility
6322                    .resolve_call_return_binding_without_arity(
6323                        analyzer,
6324                        file,
6325                        function_text,
6326                        lexical_namespace.as_deref(),
6327                        direct_type_binding
6328                            .as_ref()
6329                            .and_then(|binding| binding.unit.as_ref()),
6330                    );
6331                if let Some(binding) = callable_binding {
6332                    return Some(binding);
6333                }
6334                if has_callable {
6335                    return None;
6336                }
6337                return direct_type_binding;
6338            }
6339            let arity = visibility.call_arity_evidence(file, node, source).exact()?;
6340            let direct_type_binding_for_call = direct_type_binding.clone();
6341            resolve_static_method_call_return_binding(
6342                analyzer, visibility, file, source, function, arity,
6343            )
6344            .or(direct_type_binding)
6345            .or_else(|| {
6346                visibility.resolve_call_return_binding(
6347                    analyzer,
6348                    file,
6349                    function_text,
6350                    arity,
6351                    enclosing_namespace_context(node, source).as_deref(),
6352                    direct_type_binding_for_call
6353                        .as_ref()
6354                        .and_then(|binding| binding.unit.as_ref()),
6355                )
6356            })
6357            .or_else(|| {
6358                resolve_field_method_call_return_binding(
6359                    analyzer,
6360                    visibility,
6361                    file,
6362                    source,
6363                    function,
6364                    arity,
6365                    receiver_resolver,
6366                )
6367            })
6368        }),
6369        _ => None,
6370    }
6371}
6372
6373fn resolve_static_method_call_return_binding(
6374    analyzer: &CppGraphSource<'_>,
6375    visibility: &VisibilityIndex<'_>,
6376    file: &ProjectFile,
6377    source: &str,
6378    function: Node<'_>,
6379    arity: usize,
6380) -> Option<CppScanBinding> {
6381    if function.kind() != "qualified_identifier" {
6382        return None;
6383    }
6384    let qualified = normalize_cpp_reference_text(node_text(function, source));
6385    // A C++ qualified-id is `::`-joined with no embedded delimiters in any
6386    // single component (the shared splitter's operator-token merge keeps
6387    // `operator+`-style names intact), so re-tokenizing with the shared
6388    // structured splitter and peeling the terminal segment reproduces
6389    // `rsplit_once("::")`'s (owner, member) split exactly — same shape as
6390    // `cpp_out_of_line_function_owner`'s `qualified` split above.
6391    let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6392        brokk_bifrost_core::analyzer::Language::Cpp,
6393        &qualified,
6394    );
6395    let (owner_text, member_name) = match parts.split_last() {
6396        Some((member, owner_parts)) if !owner_parts.is_empty() => {
6397            (owner_parts.join("::"), member.clone())
6398        }
6399        _ => {
6400            let scope = function.child_by_field_name("scope")?;
6401            let name = function.child_by_field_name("name")?;
6402            (
6403                node_text(scope, source).to_string(),
6404                node_text(name, source).to_string(),
6405            )
6406        }
6407    };
6408    let owner = visibility.resolve_type(file, &owner_text)?;
6409    let candidates = visibility
6410        .visible_members_for_owner_name(file, &owner, &member_name)
6411        .into_iter()
6412        .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
6413        .cloned()
6414        .collect::<Vec<_>>();
6415    unanimous_return_binding(analyzer, visibility, file, &candidates)
6416}
6417
6418fn resolve_field_method_call_return_binding(
6419    analyzer: &CppGraphSource<'_>,
6420    visibility: &VisibilityIndex<'_>,
6421    file: &ProjectFile,
6422    source: &str,
6423    function: Node<'_>,
6424    arity: usize,
6425    receiver_resolver: Option<&ReceiverResolver<'_>>,
6426) -> Option<CppScanBinding> {
6427    if function.kind() != "field_expression" {
6428        return None;
6429    }
6430    let receiver_resolver = receiver_resolver?;
6431    let field = function.child_by_field_name("field")?;
6432    let member_name = node_text(function_terminal_node(field), source);
6433    let receiver = function
6434        .child_by_field_name("argument")
6435        .or_else(|| function.named_child(0))?;
6436    let owners = receiver_resolver(receiver, source);
6437    let mut candidates = Vec::new();
6438    for owner in owners {
6439        let declaring_owner =
6440            match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
6441                EnclosingMemberOwnerResolution::Owner(owner) => owner,
6442                EnclosingMemberOwnerResolution::Missing => continue,
6443                EnclosingMemberOwnerResolution::Ambiguous => return None,
6444            };
6445        candidates.extend(
6446            visibility
6447                .visible_members_for_owner_name(file, &declaring_owner, member_name)
6448                .into_iter()
6449                .filter(|unit| {
6450                    unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
6451                })
6452                .cloned(),
6453        );
6454    }
6455    unanimous_return_binding(analyzer, visibility, file, &candidates)
6456}
6457
6458fn unanimous_return_binding(
6459    analyzer: &CppGraphSource<'_>,
6460    visibility: &VisibilityIndex<'_>,
6461    file: &ProjectFile,
6462    candidates: &[CodeUnit],
6463) -> Option<CppScanBinding> {
6464    let mut resolved_return: Option<CppScanBinding> = None;
6465    for function in candidates {
6466        let metadata = analyzer.signature_metadata(function);
6467        let return_types = if metadata.is_empty() {
6468            vec![cpp_function_return_type_text(analyzer, function)?]
6469        } else {
6470            metadata
6471                .iter()
6472                .map(|metadata| metadata.return_type_text().map(str::to_string))
6473                .collect::<Option<Vec<_>>>()?
6474        };
6475        for return_text in return_types {
6476            let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
6477            let name = normalize_cpp_type_name(&return_text);
6478            let binding = CppScanBinding::from_type_name(
6479                name.clone(),
6480                visibility
6481                    .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
6482                indirection,
6483            );
6484            if let Some(existing) = resolved_return.as_ref()
6485                && (existing.indirection != binding.indirection
6486                    || match (&existing.unit, &binding.unit) {
6487                        (Some(left), Some(right)) => !same_visible_symbol(left, right),
6488                        (None, None) => existing.type_name != binding.type_name,
6489                        (Some(_), None) | (None, Some(_)) => true,
6490                    })
6491            {
6492                return None;
6493            }
6494            resolved_return = Some(binding);
6495        }
6496    }
6497    resolved_return
6498}
6499
6500fn aliases_from_prepared_source(cpp: &dyn CppSource, file: &ProjectFile) -> Vec<CppAlias> {
6501    let Some(prepared) = cpp.prepared_syntax(file) else {
6502        return Vec::new();
6503    };
6504    let mut aliases = Vec::new();
6505    collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
6506    aliases
6507}
6508
6509fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
6510    let mut stack = vec![root];
6511    while let Some(node) = stack.pop() {
6512        match node.kind() {
6513            "alias_declaration" if alias_has_visible_file_scope(node) => {
6514                if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
6515                    out.push(alias);
6516                }
6517            }
6518            "type_definition" if alias_has_visible_file_scope(node) => {
6519                collect_typedef_aliases(node, source, out)
6520            }
6521            _ => {}
6522        }
6523
6524        for index in (0..node.named_child_count()).rev() {
6525            if let Some(child) = node.named_child(index) {
6526                stack.push(child);
6527            }
6528        }
6529    }
6530}
6531
6532fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
6533    let mut current = node.parent();
6534    while let Some(parent) = current {
6535        match parent.kind() {
6536            "translation_unit"
6537            | "namespace_definition"
6538            | "declaration_list"
6539            | "linkage_specification" => current = parent.parent(),
6540            "template_declaration" => current = parent.parent(),
6541            _ => return false,
6542        }
6543    }
6544    true
6545}
6546
6547fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
6548    let name = node
6549        .child_by_field_name("name")
6550        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
6551    let target = node
6552        .child_by_field_name("type")
6553        .and_then(|node| normalize_reference_name(node_text(node, source)))?;
6554    Some(CppAlias {
6555        name,
6556        target,
6557        namespace: enclosing_namespace_context(node, source),
6558    })
6559}
6560
6561fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
6562    let Some(type_node) = node.child_by_field_name("type") else {
6563        return;
6564    };
6565    let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
6566        return;
6567    };
6568
6569    let mut cursor = node.walk();
6570    for child in node.named_children(&mut cursor) {
6571        if same_node(child, type_node) {
6572            continue;
6573        }
6574        if let Some(name) = extract_typedef_declarator_name(child, source) {
6575            out.push(CppAlias {
6576                name,
6577                target: target.clone(),
6578                namespace: enclosing_namespace_context(node, source),
6579            });
6580        }
6581    }
6582}
6583
6584fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
6585    match node.kind() {
6586        "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
6587            normalize_reference_name(node_text(node, source))
6588        }
6589        _ => node
6590            .child_by_field_name("declarator")
6591            .or_else(|| node.child_by_field_name("name"))
6592            .or_else(|| last_named_child(node))
6593            .and_then(|child| extract_typedef_declarator_name(child, source)),
6594    }
6595}
6596
6597fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
6598    let count = node.named_child_count();
6599    if count == 0 {
6600        None
6601    } else {
6602        node.named_child(count - 1)
6603    }
6604}
6605
6606pub fn collect_include_closure(
6607    analyzer: &CppGraphSource<'_>,
6608    include_targets: &IncludeTargetIndex,
6609    file: &ProjectFile,
6610    out: &mut HashSet<ProjectFile>,
6611    cancellation: Option<&CancellationToken>,
6612) {
6613    let mut stack = vec![file.clone()];
6614    while let Some(file) = stack.pop() {
6615        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6616            break;
6617        }
6618        if !out.insert(file.clone()) {
6619            continue;
6620        }
6621        let imports = analyzer.import_statements(&file);
6622        for include in cpp_include_paths(&imports) {
6623            for target in resolve_include_targets_with_index(&file, &include, include_targets) {
6624                stack.push(target);
6625            }
6626        }
6627    }
6628}
6629
6630fn collect_visible_declarations(
6631    include_graph: &IncludeGraph,
6632    declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
6633    file: &ProjectFile,
6634    visited: &mut HashSet<ProjectFile>,
6635    out: &mut HashSet<CodeUnit>,
6636    cancellation: Option<&CancellationToken>,
6637) {
6638    let mut stack = vec![file.clone()];
6639    while let Some(file) = stack.pop() {
6640        if cancellation.is_some_and(CancellationToken::is_cancelled) {
6641            break;
6642        }
6643        if !visited.insert(file.clone()) {
6644            continue;
6645        }
6646        if let Some(declarations) = declarations_by_file.get(&file) {
6647            out.extend(declarations.iter().cloned());
6648        }
6649        stack.extend(include_graph.targets(&file).iter().cloned());
6650    }
6651}
6652
6653pub fn signature_arity(signature: Option<&str>) -> usize {
6654    let Some(signature) = signature else {
6655        return 0;
6656    };
6657    let inner = signature
6658        .find('(')
6659        .and_then(|open| {
6660            signature[open + 1..]
6661                .find(')')
6662                .map(|close| &signature[open + 1..open + 1 + close])
6663        })
6664        .unwrap_or(signature)
6665        .trim();
6666    if inner.is_empty() || inner == "void" {
6667        return 0;
6668    }
6669    cpp_split_top_level_commas(inner).count()
6670}
6671
6672fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
6673    let source = format!("void __bifrost_macro_parameters({replacement});");
6674    let mut parser = Parser::new();
6675    parser
6676        .set_language(&tree_sitter_cpp::LANGUAGE.into())
6677        .ok()?;
6678    let tree = parser.parse(&source, None)?;
6679    let root = tree.root_node();
6680    if root.has_error() {
6681        return None;
6682    }
6683    let declaration = root.named_child(0)?;
6684    let declarator = declaration.child_by_field_name("declarator")?;
6685    let parameters = declarator.child_by_field_name("parameters")?;
6686    let mut required = 0;
6687    let mut total = 0;
6688    let mut repeated = false;
6689    let mut cursor = parameters.walk();
6690    for parameter in parameters.children(&mut cursor) {
6691        match parameter.kind() {
6692            "parameter_declaration" => {
6693                if parameter.child_by_field_name("declarator").is_none()
6694                    && parameter
6695                        .child_by_field_name("type")
6696                        .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
6697                {
6698                    continue;
6699                }
6700                required += 1;
6701                total += 1;
6702            }
6703            "optional_parameter_declaration" => total += 1,
6704            "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
6705                repeated = true;
6706            }
6707            _ => {}
6708        }
6709    }
6710    Some(CallableArity::new(required, total, repeated))
6711}
6712
6713pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
6714    analyzer
6715        .signature_metadata(unit)
6716        .into_iter()
6717        .find_map(|metadata| metadata.callable_arity())
6718        .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
6719}
6720
6721fn merge_compatible_callable_arities(
6722    left: CallableArity,
6723    right: CallableArity,
6724) -> Option<CallableArity> {
6725    let total = left.total();
6726    let left_repeated = left.accepts(total.saturating_add(1));
6727    let right_repeated = right.accepts(right.total().saturating_add(1));
6728    if total != right.total() || left_repeated != right_repeated {
6729        return None;
6730    }
6731    let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
6732    Some(CallableArity::new(required, total, left_repeated))
6733}
6734
6735fn find_include_activation(
6736    cpp: &dyn CppSource,
6737    file: &ProjectFile,
6738    prepared: &PreparedSyntaxTree,
6739    donor_source: &ProjectFile,
6740) -> Option<usize> {
6741    let include_targets = cpp.include_target_index();
6742    let mut direct_includes = Vec::new();
6743    let mut nodes = vec![prepared.tree().root_node()];
6744    // An include activates for the whole file, so only an unconditional
6745    // directive counts here.
6746    let reference = CallableReferenceContext {
6747        file,
6748        position: None,
6749    };
6750    while let Some(node) = nodes.pop() {
6751        if node.kind() == "preproc_include" {
6752            if callable_preprocessor_context_is_visible_for_reference(
6753                node,
6754                prepared.source(),
6755                &reference,
6756            ) {
6757                let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
6758                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
6759                    if let Some(target) = unique_include_target(resolve_include_targets_with_index(
6760                        file,
6761                        &include,
6762                        include_targets,
6763                    )) {
6764                        direct_includes.push((node.end_byte(), target));
6765                    }
6766                }
6767            }
6768            continue;
6769        }
6770        for index in (0..node.named_child_count()).rev() {
6771            if let Some(child) = node.named_child(index) {
6772                nodes.push(child);
6773            }
6774        }
6775    }
6776    direct_includes.sort_by_key(|(activation, _)| *activation);
6777    let mut known_missing = HashSet::default();
6778    direct_includes
6779        .into_iter()
6780        .find(|(_, direct)| {
6781            unconditional_include_reaches(
6782                cpp,
6783                include_targets,
6784                direct,
6785                donor_source,
6786                file,
6787                &mut known_missing,
6788            )
6789        })
6790        .map(|(activation, _)| activation)
6791}
6792
6793fn find_conditional_include_projection_index(
6794    cpp: &dyn CppSource,
6795    file: &ProjectFile,
6796    prepared: &PreparedSyntaxTree,
6797    on_state: &dyn Fn(),
6798) -> ConditionalIncludeProjectionIndex {
6799    let include_targets = cpp.include_target_index();
6800    let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
6801        HashMap::default();
6802    let mut pending = Vec::new();
6803    let mut nodes = vec![prepared.tree().root_node()];
6804    while let Some(node) = nodes.pop() {
6805        if node.kind() == "preproc_include" {
6806            let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
6807            else {
6808                continue;
6809            };
6810            let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
6811            for include in cpp_include_paths(std::slice::from_ref(&raw)) {
6812                let Some(target) = unique_include_target(resolve_include_targets_with_index(
6813                    file,
6814                    &include,
6815                    include_targets,
6816                )) else {
6817                    continue;
6818                };
6819                pending.push((target, node.end_byte(), required_guards.clone()));
6820            }
6821            continue;
6822        }
6823        for index in (0..node.named_child_count()).rev() {
6824            if let Some(child) = node.named_child(index) {
6825                nodes.push(child);
6826            }
6827        }
6828    }
6829
6830    // One reached file can have several distinct compatible guard paths. A
6831    // state is expanded once for each exact guard set and top-level activation
6832    // byte; this preserves those paths while terminating include cycles.
6833    let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
6834        HashMap::default();
6835    while let Some((current_file, activation_byte, required_guards)) = pending.pop() {
6836        let guard_sets = expanded
6837            .entry((current_file.clone(), activation_byte))
6838            .or_default();
6839        if guard_sets.contains(&required_guards) {
6840            continue;
6841        }
6842        guard_sets.push(required_guards.clone());
6843        on_state();
6844
6845        let projections = projections_by_source
6846            .entry(current_file.clone())
6847            .or_default();
6848        if !projections.iter().any(|projection| {
6849            projection.activation_byte == activation_byte
6850                && projection.required_guards == required_guards
6851        }) {
6852            projections.push(ConditionalIncludeProjection {
6853                activation_byte,
6854                required_guards: required_guards.clone(),
6855            });
6856        }
6857
6858        let Some(current_prepared) = cpp.prepared_syntax(&current_file) else {
6859            continue;
6860        };
6861        let mut nodes = vec![current_prepared.tree().root_node()];
6862        while let Some(node) = nodes.pop() {
6863            if node.kind() == "preproc_include" {
6864                let Some(include_guards) =
6865                    preprocessor_guard_environment(node, current_prepared.source())
6866                else {
6867                    continue;
6868                };
6869                let Some(path_guards) =
6870                    merge_preprocessor_guards(&required_guards, &include_guards)
6871                else {
6872                    continue;
6873                };
6874                let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
6875                for include in cpp_include_paths(std::slice::from_ref(&raw)) {
6876                    let Some(target) = unique_include_target(resolve_include_targets_with_index(
6877                        &current_file,
6878                        &include,
6879                        include_targets,
6880                    )) else {
6881                        continue;
6882                    };
6883                    pending.push((target, activation_byte, path_guards.clone()));
6884                }
6885                continue;
6886            }
6887            for index in (0..node.named_child_count()).rev() {
6888                if let Some(child) = node.named_child(index) {
6889                    nodes.push(child);
6890                }
6891            }
6892        }
6893    }
6894
6895    projections_by_source
6896        .into_iter()
6897        .map(|(source, mut projections)| {
6898            projections.sort_by_key(|projection| projection.activation_byte);
6899            (source, Arc::from(projections))
6900        })
6901        .collect()
6902}
6903
6904fn unconditional_include_reaches(
6905    cpp: &dyn CppSource,
6906    include_targets: &IncludeTargetIndex,
6907    first: &ProjectFile,
6908    donor_source: &ProjectFile,
6909    reference_file: &ProjectFile,
6910    known_missing: &mut HashSet<ProjectFile>,
6911) -> bool {
6912    if first == donor_source {
6913        return true;
6914    }
6915    if known_missing.contains(first) {
6916        return false;
6917    }
6918    let reference_is_c = reference_file
6919        .rel_path()
6920        .extension()
6921        .and_then(|extension| extension.to_str())
6922        == Some("c");
6923    if let Some(reaches) =
6924        cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
6925    {
6926        return reaches;
6927    }
6928    let mut visited = HashSet::default();
6929    let mut files = vec![first.clone()];
6930    // Only an unconditional directive extends the include reach, so the walk
6931    // asks the question without a reference position.
6932    let reference = CallableReferenceContext {
6933        file: reference_file,
6934        position: None,
6935    };
6936    while let Some(file) = files.pop() {
6937        if file == *donor_source {
6938            cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
6939            return true;
6940        }
6941        if known_missing.contains(&file) || !visited.insert(file.clone()) {
6942            continue;
6943        }
6944        let Some(prepared) = cpp.prepared_syntax(&file) else {
6945            continue;
6946        };
6947        let mut nodes = vec![prepared.tree().root_node()];
6948        while let Some(node) = nodes.pop() {
6949            if node.kind() == "preproc_include" {
6950                if callable_preprocessor_context_is_visible_for_reference(
6951                    node,
6952                    prepared.source(),
6953                    &reference,
6954                ) {
6955                    let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
6956                    for include in cpp_include_paths(std::slice::from_ref(&raw)) {
6957                        if let Some(target) = unique_include_target(
6958                            resolve_include_targets_with_index(&file, &include, include_targets),
6959                        ) {
6960                            files.push(target);
6961                        }
6962                    }
6963                }
6964                continue;
6965            }
6966            for index in (0..node.named_child_count()).rev() {
6967                if let Some(child) = node.named_child(index) {
6968                    nodes.push(child);
6969                }
6970            }
6971        }
6972    }
6973    known_missing.extend(visited);
6974    cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
6975    false
6976}
6977
6978fn declaration_guard_requirements(
6979    analyzer: &CppGraphSource<'_>,
6980    cpp: &dyn CppSource,
6981    candidate: &CodeUnit,
6982) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
6983    let Some(prepared) = cpp.prepared_syntax(candidate.source()) else {
6984        return Vec::new();
6985    };
6986    let root = prepared.tree().root_node();
6987    analyzer
6988        .ranges(candidate)
6989        .into_iter()
6990        .filter_map(|range| {
6991            root.descendant_for_byte_range(range.start_byte, range.end_byte)
6992                .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
6993                // A class name is injected into its own body at the declaration's
6994                // introduction point, not after the complete class range. Using
6995                // the start also preserves normal before/after ordering for aliases.
6996                .map(|required| (range.start_byte, required))
6997        })
6998        .collect()
6999}
7000
7001fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
7002    analyzer
7003        .ranges(candidate)
7004        .into_iter()
7005        .map(|range| range.start_byte)
7006        .min()
7007}
7008
7009fn guard_requirements_hold_at_reference(
7010    required: &HashSet<PreprocessorGuard>,
7011    reference: Option<&HashSet<PreprocessorGuard>>,
7012) -> bool {
7013    reference.is_some_and(|active| {
7014        required
7015            .iter()
7016            .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
7017    })
7018}
7019
7020fn preprocessor_guard_holds_at_reference(
7021    required: &PreprocessorGuard,
7022    active: &HashSet<PreprocessorGuard>,
7023) -> bool {
7024    if active.contains(required) {
7025        return true;
7026    }
7027    let active_expression = BooleanGuardExpression::all(
7028        active
7029            .iter()
7030            .filter_map(PreprocessorGuard::as_boolean_expression),
7031    );
7032    required
7033        .as_boolean_expression()
7034        .is_some_and(|required| active_expression.implies(&required))
7035}
7036
7037/// Cross-file guard rule: two guard sets are compatible when neither one
7038/// contradicts the other. Use this instead of the subset test whenever the
7039/// guards come from a foreign file, which resolves its own conditionals
7040/// independently of the reference.
7041fn guards_compatible_at_reference(
7042    declaration: &HashSet<PreprocessorGuard>,
7043    reference: Option<&HashSet<PreprocessorGuard>>,
7044) -> bool {
7045    reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
7046}
7047
7048/// The byte range of the `#if`/`#elif`/`#else` chain that encloses the smallest
7049/// node covering `[start_byte, end_byte)`, or `None` when nothing there is
7050/// conditional.
7051///
7052/// Two declarations of one name that report the same chain stand in different
7053/// branches of it, so at most one of them is compiled in any configuration.
7054/// They are alternate spellings of a single declaration, not competing
7055/// declarations, and navigation must not present them as an ambiguity.
7056pub fn preprocessor_conditional_family_range(
7057    root: Node<'_>,
7058    start_byte: usize,
7059    end_byte: usize,
7060) -> Option<(usize, usize)> {
7061    let node = root.descendant_for_byte_range(start_byte, end_byte)?;
7062    let mut ancestor = Some(node);
7063    while let Some(current) = ancestor {
7064        if is_preprocessor_conditional(current)
7065            && preprocessor_conditional_contains_descendant(current, node)
7066        {
7067            let family = preprocessor_conditional_family_root(current);
7068            return Some((family.start_byte(), family.end_byte()));
7069        }
7070        ancestor = current.parent();
7071    }
7072    None
7073}
7074
7075fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
7076    let mut ancestor = node.parent();
7077    while let Some(current) = ancestor {
7078        if is_preprocessor_conditional(current)
7079            && preprocessor_conditional_contains_descendant(current, node)
7080        {
7081            let family = preprocessor_conditional_family_root(current);
7082            if preprocessor_conditional_family_has_terminal_else(family) {
7083                return Some(family);
7084            }
7085        }
7086        ancestor = current.parent();
7087    }
7088    None
7089}
7090
7091fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
7092    while let Some(parent) = conditional.parent() {
7093        let is_alternative = parent
7094            .child_by_field_name("alternative")
7095            .is_some_and(|alternative| {
7096                alternative.start_byte() == conditional.start_byte()
7097                    && alternative.end_byte() == conditional.end_byte()
7098            });
7099        if !is_alternative {
7100            break;
7101        }
7102        conditional = parent;
7103    }
7104    conditional
7105}
7106
7107fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
7108    loop {
7109        let Some(alternative) = conditional.child_by_field_name("alternative") else {
7110            return false;
7111        };
7112        match alternative.kind() {
7113            "preproc_else" => return true,
7114            "preproc_elif" => conditional = alternative,
7115            _ => return false,
7116        }
7117    }
7118}
7119
7120pub fn preprocessor_guard_environment(
7121    node: Node<'_>,
7122    source: &str,
7123) -> Option<HashSet<PreprocessorGuard>> {
7124    let mut guards = HashSet::default();
7125    let mut ancestor = node.parent();
7126    while let Some(conditional) = ancestor {
7127        if matches!(
7128            conditional.kind(),
7129            "preproc_if" | "preproc_ifdef" | "preproc_elif"
7130        ) && !is_file_covering_include_guard(conditional, source)
7131            && preprocessor_conditional_contains_descendant(conditional, node)
7132        {
7133            let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
7134            match guard {
7135                PreprocessorGuard::Constant(true) => {
7136                    ancestor = conditional.parent();
7137                    continue;
7138                }
7139                PreprocessorGuard::Constant(false) => return None,
7140                _ => {}
7141            }
7142            if guards.contains(&guard.negated()) {
7143                return None;
7144            }
7145            guards.insert(guard);
7146        }
7147        ancestor = conditional.parent();
7148    }
7149    if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
7150        match guard {
7151            PreprocessorGuard::Constant(true) => {}
7152            PreprocessorGuard::Constant(false) => return None,
7153            _ => {
7154                if guards.contains(&guard.negated()) {
7155                    return None;
7156                }
7157                guards.insert(guard);
7158            }
7159        }
7160    }
7161    Some(guards)
7162}
7163
7164fn fragmented_statement_preprocessor_guard(
7165    descendant: Node<'_>,
7166    source: &str,
7167) -> Option<PreprocessorGuard> {
7168    // A conditional that starts before `} else if (...) {` crosses the
7169    // enclosing statement's grammar boundary. tree-sitter leaves its opener
7170    // as a `preproc_if` with a missing terminator in the consequence and
7171    // reparses the real `#endif` as a `preproc_call` in the alternative. Pair
7172    // those structured nodes before restoring the guard to intervening uses.
7173    let mut ancestor = descendant.parent();
7174    while let Some(statement) = ancestor {
7175        if statement.kind() == "if_statement"
7176            && let (Some(consequence), Some(alternative)) = (
7177                statement.child_by_field_name("consequence"),
7178                statement.child_by_field_name("alternative"),
7179            )
7180            && alternative.start_byte() <= descendant.start_byte()
7181            && descendant.end_byte() <= alternative.end_byte()
7182        {
7183            let mut cursor = consequence.walk();
7184            let openers = consequence
7185                .named_children(&mut cursor)
7186                .filter(|child| {
7187                    matches!(child.kind(), "preproc_if" | "preproc_ifdef")
7188                        && child
7189                            .child(child.child_count().saturating_sub(1))
7190                            .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
7191                })
7192                .collect::<Vec<_>>();
7193            if openers.len() != 1 {
7194                ancestor = statement.parent();
7195                continue;
7196            }
7197
7198            let mut terminators = Vec::new();
7199            let mut stack = vec![alternative];
7200            while let Some(node) = stack.pop() {
7201                if node.kind() == "preproc_call"
7202                    && node.start_byte() >= descendant.end_byte()
7203                    && node
7204                        .child_by_field_name("directive")
7205                        .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
7206                {
7207                    terminators.push(node);
7208                    continue;
7209                }
7210                for index in (0..node.named_child_count()).rev() {
7211                    if let Some(child) = node.named_child(index) {
7212                        stack.push(child);
7213                    }
7214                }
7215            }
7216            if terminators.len() == 1 {
7217                return simple_preprocessor_guard(openers[0], source);
7218            }
7219        }
7220        ancestor = statement.parent();
7221    }
7222    None
7223}
7224
7225fn preprocessor_guard_for_descendant(
7226    conditional: Node<'_>,
7227    descendant: Node<'_>,
7228    source: &str,
7229) -> Option<PreprocessorGuard> {
7230    let mut guard = simple_preprocessor_guard(conditional, source)?;
7231    if conditional
7232        .child_by_field_name("alternative")
7233        .is_some_and(|alternative| {
7234            alternative.start_byte() <= descendant.start_byte()
7235                && descendant.end_byte() <= alternative.end_byte()
7236        })
7237    {
7238        let alternative = conditional.child_by_field_name("alternative")?;
7239        // Tree-sitter nests an `#elif` chain in each `alternative` field. A
7240        // descendant in any later branch must first exclude the parent branch,
7241        // then collect the nested `preproc_elif` guard from its own ancestor.
7242        if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
7243            return None;
7244        }
7245        guard = guard.negated();
7246    }
7247    Some(guard)
7248}
7249
7250fn preprocessor_conditional_contains_descendant(
7251    conditional: Node<'_>,
7252    descendant: Node<'_>,
7253) -> bool {
7254    cpp_displaced_preprocessor_boundary(conditional)
7255        .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
7256}
7257
7258pub fn merge_preprocessor_guards(
7259    left: &HashSet<PreprocessorGuard>,
7260    right: &HashSet<PreprocessorGuard>,
7261) -> Option<HashSet<PreprocessorGuard>> {
7262    let mut merged = left.clone();
7263    for guard in right {
7264        if merged.contains(&guard.negated()) {
7265            return None;
7266        }
7267        merged.insert(guard.clone());
7268    }
7269    Some(merged)
7270}
7271
7272fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
7273    if conditional.kind() == "preproc_ifdef" {
7274        let name = conditional.child_by_field_name("name")?;
7275        let name = node_text(name, source).to_string();
7276        return match conditional.child(0)?.kind() {
7277            "#ifdef" => Some(PreprocessorGuard::Defined(name)),
7278            "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
7279            _ => None,
7280        };
7281    }
7282    let condition = conditional.child_by_field_name("condition")?;
7283    simple_preprocessor_expression_guard(condition, source).or_else(|| {
7284        Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
7285            node_text(condition, source),
7286        )))
7287    })
7288}
7289
7290fn simple_preprocessor_expression_guard(
7291    expression: Node<'_>,
7292    source: &str,
7293) -> Option<PreprocessorGuard> {
7294    match expression.kind() {
7295        "number_literal" => match node_text(expression, source).trim() {
7296            "0" => Some(PreprocessorGuard::Constant(false)),
7297            "1" => Some(PreprocessorGuard::Constant(true)),
7298            _ => None,
7299        },
7300        "preproc_defined" => {
7301            let identifier = (0..expression.named_child_count())
7302                .filter_map(|index| expression.named_child(index))
7303                .find(|child| child.kind() == "identifier")?;
7304            Some(PreprocessorGuard::Defined(
7305                node_text(identifier, source).to_string(),
7306            ))
7307        }
7308        "unary_expression"
7309            if expression
7310                .child_by_field_name("operator")
7311                .is_some_and(|operator| operator.kind() == "!") =>
7312        {
7313            simple_preprocessor_expression_guard(
7314                expression.child_by_field_name("argument")?,
7315                source,
7316            )
7317            .map(|guard| guard.negated())
7318        }
7319        "parenthesized_expression" => (0..expression.named_child_count())
7320            .filter_map(|index| expression.named_child(index))
7321            .next()
7322            .and_then(|child| simple_preprocessor_expression_guard(child, source)),
7323        "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
7324            expression, source,
7325        ))),
7326        _ => None,
7327    }
7328}
7329
7330fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
7331    match expression.kind() {
7332        "number_literal" => match node_text(expression, source).trim() {
7333            "0" => BooleanGuardExpression::Constant(false),
7334            "1" => BooleanGuardExpression::Constant(true),
7335            _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7336                expression, source,
7337            ))),
7338        },
7339        "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
7340        "preproc_defined" => {
7341            let identifier = (0..expression.named_child_count())
7342                .filter_map(|index| expression.named_child(index))
7343                .find(|child| child.kind() == "identifier");
7344            identifier.map_or_else(
7345                || {
7346                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7347                        expression, source,
7348                    )))
7349                },
7350                |identifier| {
7351                    BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
7352                },
7353            )
7354        }
7355        "unary_expression"
7356            if expression
7357                .child_by_field_name("operator")
7358                .is_some_and(|operator| operator.kind() == "!") =>
7359        {
7360            expression.child_by_field_name("argument").map_or_else(
7361                || {
7362                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7363                        expression, source,
7364                    )))
7365                },
7366                |argument| boolean_preprocessor_expression(argument, source).negated(),
7367            )
7368        }
7369        "parenthesized_expression" => (0..expression.named_child_count())
7370            .filter_map(|index| expression.named_child(index))
7371            .next()
7372            .map_or_else(
7373                || {
7374                    BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7375                        expression, source,
7376                    )))
7377                },
7378                |child| boolean_preprocessor_expression(child, source),
7379            ),
7380        "binary_expression" => {
7381            let operands = || {
7382                Some((
7383                    boolean_preprocessor_expression(
7384                        expression.child_by_field_name("left")?,
7385                        source,
7386                    ),
7387                    boolean_preprocessor_expression(
7388                        expression.child_by_field_name("right")?,
7389                        source,
7390                    ),
7391                ))
7392            };
7393            match expression
7394                .child_by_field_name("operator")
7395                .map(|operator| operator.kind())
7396            {
7397                Some("&&") => operands().map_or_else(
7398                    || {
7399                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7400                            expression, source,
7401                        )))
7402                    },
7403                    |(left, right)| BooleanGuardExpression::all([left, right]),
7404                ),
7405                Some("||") => operands().map_or_else(
7406                    || {
7407                        BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7408                            expression, source,
7409                        )))
7410                    },
7411                    |(left, right)| BooleanGuardExpression::any([left, right]),
7412                ),
7413                _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
7414                    expression, source,
7415                ))),
7416            }
7417        }
7418        _ => {
7419            BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
7420        }
7421    }
7422}
7423
7424fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
7425    if targets.len() == 1 {
7426        targets.pop()
7427    } else {
7428        None
7429    }
7430}
7431
7432/// The declaration nodes of `candidate` in `prepared` that stand at a scope a
7433/// later reference can name.
7434///
7435/// A declaration inside a real function body, lambda, or nested block is block
7436/// local and is dropped. A declaration inside a parser-recovery wrapper that
7437/// merely looks callable -- an export macro between `class` and its name, or a
7438/// namespace-opening macro token before `namespace x {` -- keeps class or
7439/// namespace scope and is kept.
7440fn nameable_callable_declaration_nodes<'tree>(
7441    analyzer: &CppGraphSource<'_>,
7442    prepared: &'tree PreparedSyntaxTree,
7443    candidate: &CodeUnit,
7444) -> Vec<Node<'tree>> {
7445    let root = prepared.tree().root_node();
7446    analyzer
7447        .ranges(candidate)
7448        .into_iter()
7449        .filter_map(|range| {
7450            let mut declaration =
7451                root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
7452            while !matches!(
7453                declaration.kind(),
7454                "declaration" | "field_declaration" | "function_definition"
7455            ) {
7456                declaration = declaration.parent()?;
7457            }
7458            let mut ancestor = declaration.parent();
7459            while let Some(node) = ancestor {
7460                if node.kind() == "function_definition"
7461                    && is_recovered_declaration_scope_container(node, prepared.source())
7462                {
7463                    ancestor = node.parent();
7464                    continue;
7465                }
7466                if node.kind() == "compound_statement"
7467                    && node.parent().is_some_and(|parent| {
7468                        is_recovered_declaration_scope_container(parent, prepared.source())
7469                    })
7470                {
7471                    ancestor = node.parent().and_then(|parent| parent.parent());
7472                    continue;
7473                }
7474                if matches!(
7475                    node.kind(),
7476                    "compound_statement" | "function_definition" | "lambda_expression"
7477                ) {
7478                    return None;
7479                }
7480                ancestor = node.parent();
7481            }
7482            Some(declaration)
7483        })
7484        .collect()
7485}
7486
7487fn callable_declaration_activation_in_file(
7488    analyzer: &CppGraphSource<'_>,
7489    prepared: &PreparedSyntaxTree,
7490    candidate: &CodeUnit,
7491    reference: &CallableReferenceContext<'_>,
7492) -> Option<usize> {
7493    nameable_callable_declaration_nodes(analyzer, prepared, candidate)
7494        .into_iter()
7495        .filter(|declaration| {
7496            callable_preprocessor_context_is_visible_for_reference(
7497                *declaration,
7498                prepared.source(),
7499                reference,
7500            )
7501        })
7502        .map(callable_declaration_activation_byte)
7503        .min()
7504}
7505
7506/// C and C++ activate a declared name at the end of its declarator, not at the
7507/// end of the whole declaration. A function definition ends at the closing
7508/// brace of its body, so the declaration end byte would hide the function from
7509/// its own body and make self recursion unresolvable without a prototype.
7510fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
7511    if declaration.kind() != "function_definition" {
7512        return declaration.end_byte();
7513    }
7514    declaration
7515        .child_by_field_name("declarator")
7516        .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
7517}
7518
7519/// The reference side of a callable visibility question.
7520///
7521/// An include-graph walk and a whole-file arity activation ask the question
7522/// without one reference position, so they carry no `position` and therefore no
7523/// guard environment.
7524struct CallableReferenceContext<'a> {
7525    file: &'a ProjectFile,
7526    position: Option<CallableReferencePosition<'a>>,
7527}
7528
7529/// One reference position plus its preprocessor guard environment. The
7530/// environment is computed on demand because most declarations carry no
7531/// non-trivial guard.
7532struct CallableReferencePosition<'a> {
7533    prepared: &'a PreparedSyntaxTree,
7534    byte: usize,
7535    guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
7536}
7537
7538impl CallableReferenceContext<'_> {
7539    fn is_c(&self) -> bool {
7540        self.file
7541            .rel_path()
7542            .extension()
7543            .and_then(|extension| extension.to_str())
7544            == Some("c")
7545    }
7546
7547    fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
7548        let position = self.position.as_ref()?;
7549        position
7550            .guards
7551            .get_or_init(|| {
7552                position
7553                    .prepared
7554                    .tree()
7555                    .root_node()
7556                    .descendant_for_byte_range(position.byte, position.byte)
7557                    .and_then(|node| {
7558                        preprocessor_guard_environment(node, position.prepared.source())
7559                    })
7560            })
7561            .as_ref()
7562    }
7563}
7564
7565fn callable_preprocessor_context_is_visible_for_reference(
7566    node: Node<'_>,
7567    source: &str,
7568    reference: &CallableReferenceContext<'_>,
7569) -> bool {
7570    let reference_is_c = reference.is_c();
7571    let mut ancestor = node.parent();
7572    while let Some(conditional) = ancestor {
7573        if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
7574            && !is_file_covering_include_guard(conditional, source)
7575            && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
7576            && preprocessor_conditional_contains_descendant(conditional, node)
7577        {
7578            let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
7579                return false;
7580            };
7581            match guard {
7582                PreprocessorGuard::Constant(true) => {}
7583                PreprocessorGuard::Constant(false) => return false,
7584                PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
7585                    if reference_is_c {
7586                        return false;
7587                    }
7588                }
7589                PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
7590                    if !reference_is_c {
7591                        return false;
7592                    }
7593                }
7594                // The declaration stands under a guard whose value this
7595                // analyzer cannot decide. It is still co-active with a
7596                // reference whose active guards imply it. Collecting one guard
7597                // per ancestor makes the whole walk a conjunction of the
7598                // declaration requirements.
7599                guard => {
7600                    if !reference
7601                        .guards()
7602                        .is_some_and(|active| preprocessor_guard_holds_at_reference(&guard, active))
7603                    {
7604                        return false;
7605                    }
7606                }
7607            }
7608        }
7609        ancestor = conditional.parent();
7610    }
7611    true
7612}
7613
7614fn flattened_macro_namespace_declaration_matches(
7615    analyzer: &CppGraphSource<'_>,
7616    cpp: &dyn CppSource,
7617    reference_file: &ProjectFile,
7618    visible_declaration: &CodeUnit,
7619    qualified_candidate: &CodeUnit,
7620    reference_byte: usize,
7621) -> bool {
7622    // Namespace-opening macros can leave tree-sitter unable to retain the
7623    // namespace owner after a later recovery point. In that shape the forward
7624    // declaration is indexed at translation-unit scope, while the definition
7625    // still has its qualified owner. Require all surviving structural evidence
7626    // before treating the declaration as activation for that definition.
7627    if visible_declaration.kind() != qualified_candidate.kind()
7628        || visible_declaration.identifier() != qualified_candidate.identifier()
7629        || visible_declaration.signature() != qualified_candidate.signature()
7630        || !visible_declaration.package_name().is_empty()
7631        || qualified_candidate.package_name().is_empty()
7632    {
7633        return false;
7634    }
7635
7636    let Some(prepared) = cpp.prepared_syntax(visible_declaration.source()) else {
7637        return false;
7638    };
7639    let root = prepared.tree().root_node();
7640    let closing_brace_limit = if visible_declaration.source() == reference_file {
7641        reference_byte
7642    } else {
7643        usize::MAX
7644    };
7645
7646    analyzer
7647        .ranges(visible_declaration)
7648        .into_iter()
7649        .any(|range| {
7650            let Some(mut declaration) =
7651                root.descendant_for_byte_range(range.start_byte, range.end_byte)
7652            else {
7653                return false;
7654            };
7655            while !matches!(
7656                declaration.kind(),
7657                "declaration" | "field_declaration" | "function_definition"
7658            ) {
7659                let Some(parent) = declaration.parent() else {
7660                    return false;
7661                };
7662                declaration = parent;
7663            }
7664            if declaration
7665                .parent()
7666                .is_none_or(|parent| parent.kind() != "translation_unit")
7667                || !macro_displaced_cpp_return_type(declaration, prepared.source())
7668            {
7669                return false;
7670            }
7671
7672            let mut cursor = root.walk();
7673            root.named_children(&mut cursor).any(|sibling| {
7674                sibling.start_byte() >= declaration.end_byte()
7675                    && sibling.start_byte() < closing_brace_limit
7676                    && direct_unmatched_closing_brace(sibling)
7677            })
7678        })
7679}
7680
7681fn flattened_macro_namespace_components(
7682    declaration: Node<'_>,
7683    source: &str,
7684) -> Option<Vec<String>> {
7685    flattened_macro_function_namespace_components(declaration, source)
7686        .or_else(|| flattened_macro_error_namespace_components(declaration, source))
7687}
7688
7689fn flattened_macro_function_namespace_components(
7690    declaration: Node<'_>,
7691    source: &str,
7692) -> Option<Vec<String>> {
7693    let body = declaration
7694        .parent()
7695        .filter(|parent| parent.kind() == "compound_statement")?;
7696    let function = body.parent()?;
7697    if function.child_by_field_name("body") != Some(body) {
7698        return None;
7699    }
7700    let namespace_name = recovered_macro_namespace_name(function, source)?;
7701    let mut components = enclosing_namespace_components(declaration, source)?;
7702    components.push(namespace_name);
7703    Some(components)
7704}
7705
7706/// The namespace name a namespace-opening macro token displaced into a
7707/// synthetic `function_definition`, or `None` when `function` is not that
7708/// recovery shape.
7709///
7710/// `ABSL_NAMESPACE_BEGIN` (or `FMT_BEGIN_NAMESPACE`, ...) immediately before
7711/// `namespace x {` leaves tree-sitter with a `function_definition` whose type is
7712/// the macro token, whose declarator is the namespace name behind an `ERROR`
7713/// holding the `namespace` keyword, and whose body spans the whole namespace
7714/// region. The matching `*_NAMESPACE_END` sibling is what separates the recovery
7715/// artifact from a real function definition.
7716fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
7717    if function.kind() != "function_definition" || !function.has_error() {
7718        return None;
7719    }
7720    let body = function
7721        .child_by_field_name("body")
7722        .filter(|body| body.kind() == "compound_statement")?;
7723    let mut cursor = function.walk();
7724    let prefix = function
7725        .named_children(&mut cursor)
7726        .take_while(|child| child.start_byte() < body.start_byte())
7727        .filter(|child| child.kind() != "comment")
7728        .collect::<Vec<_>>();
7729    let begin_index = prefix.iter().rposition(|child| {
7730        flattened_macro_sentinel_name(*child, source)
7731            .is_some_and(|name| is_namespace_begin_sentinel(&name))
7732    })?;
7733    let mut identifiers = Vec::new();
7734    let mut stack = prefix[begin_index + 1..]
7735        .iter()
7736        .rev()
7737        .copied()
7738        .collect::<Vec<_>>();
7739    while let Some(current) = stack.pop() {
7740        if let Some(identifier) = direct_cpp_identifier_name(current, source) {
7741            identifiers.push(identifier);
7742            continue;
7743        }
7744        let mut cursor = current.walk();
7745        let children = current.named_children(&mut cursor).collect::<Vec<_>>();
7746        stack.extend(children.into_iter().rev());
7747    }
7748    let [keyword, namespace_name] = identifiers.as_slice() else {
7749        return None;
7750    };
7751    if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
7752    {
7753        return None;
7754    }
7755    let mut next = function.next_named_sibling();
7756    let next = loop {
7757        let candidate = next?;
7758        next = candidate.next_named_sibling();
7759        if candidate.kind() != "comment" {
7760            break candidate;
7761        }
7762    };
7763    flattened_macro_sentinel_name(next, source)
7764        .is_some_and(|name| is_namespace_end_sentinel(&name))
7765        .then(|| namespace_name.clone())
7766}
7767
7768/// A `function_definition` that exists only because tree-sitter recovered a
7769/// macro-decorated class head or a namespace-opening macro token. A declaration
7770/// in such a body keeps class or namespace scope, so a scope walk must step over
7771/// the wrapper instead of treating the declaration as block local.
7772fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
7773    crate::declarations::is_recovered_exported_class_container(node, source)
7774        || recovered_macro_namespace_name(node, source).is_some()
7775}
7776
7777fn flattened_macro_error_namespace_components(
7778    declaration: Node<'_>,
7779    source: &str,
7780) -> Option<Vec<String>> {
7781    let parent = declaration
7782        .parent()
7783        .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
7784    let mut cursor = parent.walk();
7785    let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
7786    let declaration_index = siblings
7787        .iter()
7788        .position(|candidate| same_node(*candidate, declaration))?;
7789    let begin_index = (0..declaration_index).rev().find(|index| {
7790        flattened_macro_sentinel_name(siblings[*index], source)
7791            .is_some_and(|name| is_namespace_begin_sentinel(&name))
7792    })?;
7793
7794    let significant = siblings[begin_index + 1..declaration_index]
7795        .iter()
7796        .copied()
7797        .filter(|node| node.kind() != "comment")
7798        .collect::<Vec<_>>();
7799    let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
7800        return None;
7801    };
7802    if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
7803        return None;
7804    }
7805    let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
7806    if significant[2..].iter().any(|node| {
7807        flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
7808            is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
7809        })
7810    }) {
7811        return None;
7812    }
7813
7814    let mut saw_namespace_close = false;
7815    for sibling in siblings.iter().skip(declaration_index + 1).copied() {
7816        if sibling.kind() == "comment" {
7817            continue;
7818        }
7819        if !saw_namespace_close {
7820            if direct_unmatched_closing_brace(sibling) {
7821                saw_namespace_close = true;
7822                continue;
7823            }
7824            if flattened_macro_sentinel_name(sibling, source).is_some() {
7825                return None;
7826            }
7827            continue;
7828        }
7829        if !flattened_macro_sentinel_name(sibling, source)
7830            .is_some_and(|name| is_namespace_end_sentinel(&name))
7831        {
7832            return None;
7833        }
7834        let mut components = enclosing_namespace_components(declaration, source)?;
7835        components.push(namespace_name);
7836        return Some(components);
7837    }
7838    None
7839}
7840
7841fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
7842    // At translation-unit scope the trailing `X_NAMESPACE_END` token parses as
7843    // an `expression_statement` with a missing semicolon; inside a namespace
7844    // body the same token stays a bare `type_identifier`.
7845    let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
7846        node.named_child(0)?
7847    } else {
7848        node
7849    };
7850    let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
7851        node.child_by_field_name("type")
7852            .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
7853    })?;
7854    (cpp_export_macro_token(&candidate)
7855        && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
7856    .then_some(candidate)
7857}
7858
7859/// Namespace-opening macros are spelled both ways in the wild:
7860/// `ABSL_NAMESPACE_BEGIN` (abseil, nlohmann) and `FMT_BEGIN_NAMESPACE` (fmt).
7861fn is_namespace_begin_sentinel(name: &str) -> bool {
7862    name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
7863}
7864
7865fn is_namespace_end_sentinel(name: &str) -> bool {
7866    name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
7867}
7868
7869fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
7870    if node.kind() != "ERROR" || node.named_child_count() != 1 {
7871        return None;
7872    }
7873    let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
7874    (!cpp_export_macro_token(&name)).then_some(name)
7875}
7876
7877fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
7878    if !matches!(
7879        node.kind(),
7880        "identifier" | "namespace_identifier" | "type_identifier"
7881    ) {
7882        return None;
7883    }
7884    let name = normalize_cpp_whitespace(node_text(node, source));
7885    (!name.is_empty()).then_some(name)
7886}
7887
7888fn guard_requirement_sets_match(
7889    left: &[(usize, HashSet<PreprocessorGuard>)],
7890    right: &[(usize, HashSet<PreprocessorGuard>)],
7891) -> bool {
7892    left.len() == right.len()
7893        && left.iter().all(|(_, left_guards)| {
7894            right
7895                .iter()
7896                .any(|(_, right_guards)| left_guards == right_guards)
7897        })
7898        && right.iter().all(|(_, right_guards)| {
7899            left.iter()
7900                .any(|(_, left_guards)| right_guards == left_guards)
7901        })
7902}
7903
7904fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
7905    let Some(type_node) = declaration.child_by_field_name("type") else {
7906        return false;
7907    };
7908    let type_name = normalize_cpp_whitespace(node_text(type_node, source));
7909    !type_name.is_empty()
7910        && type_name
7911            .chars()
7912            .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
7913        && (0..declaration.named_child_count()).any(|index| {
7914            declaration
7915                .named_child(index)
7916                .is_some_and(|child| child.kind() == "ERROR")
7917        })
7918}
7919
7920fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
7921    node.kind() == "ERROR"
7922        && (0..node.child_count())
7923            .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
7924}
7925
7926pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
7927    let mut ancestor = node.parent();
7928    while let Some(parent) = ancestor {
7929        if is_preprocessor_conditional(parent)
7930            && !is_file_covering_include_guard(parent, source)
7931            && !is_split_cpp_language_linkage_wrapper(parent, node, source)
7932        {
7933            return false;
7934        }
7935        ancestor = parent.parent();
7936    }
7937    true
7938}
7939
7940fn is_split_cpp_language_linkage_wrapper(
7941    conditional: Node<'_>,
7942    descendant: Node<'_>,
7943    source: &str,
7944) -> bool {
7945    if conditional.child_by_field_name("alternative").is_some()
7946        || !matches!(
7947            simple_preprocessor_guard(conditional, source),
7948            Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
7949        )
7950    {
7951        return false;
7952    }
7953    let mut current = descendant.parent();
7954    let linkage = loop {
7955        let Some(node) = current else {
7956            return false;
7957        };
7958        if node == conditional {
7959            return false;
7960        }
7961        if node.kind() == "linkage_specification" {
7962            break node;
7963        }
7964        current = node.parent();
7965    };
7966    if linkage
7967        .child_by_field_name("value")
7968        .is_none_or(|value| node_text(value, source) != "\"C\"")
7969    {
7970        return false;
7971    }
7972    let Some(body) = linkage.child_by_field_name("body") else {
7973        return false;
7974    };
7975    let closes_opening_branch = (0..body.named_child_count())
7976        .filter_map(|index| body.named_child(index))
7977        .take_while(|child| child.end_byte() <= descendant.start_byte())
7978        .any(|child| {
7979            child.kind() == "preproc_call"
7980                && child
7981                    .child_by_field_name("directive")
7982                    .is_some_and(|directive| node_text(directive, source) == "#endif")
7983        });
7984    let reopens_for_closing_brace = (0..body.named_child_count())
7985        .filter_map(|index| body.named_child(index))
7986        .skip_while(|child| child.start_byte() < descendant.end_byte())
7987        .any(|child| {
7988            matches!(
7989                simple_preprocessor_guard(child, source),
7990                Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
7991            ) && (0..child.child_count()).any(|index| {
7992                child
7993                    .child(index)
7994                    .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
7995            })
7996        });
7997    closes_opening_branch && reopens_for_closing_brace
7998}
7999
8000pub fn call_arity(node: Node<'_>) -> usize {
8001    node.child_by_field_name("arguments")
8002        .or_else(|| node.child_by_field_name("parameters"))
8003        .or_else(|| node.child_by_field_name("value"))
8004        .or_else(|| first_named_child_of_kind(node, "argument_list"))
8005        .or_else(|| first_named_child_of_kind(node, "initializer_list"))
8006        .map(|args| argument_children(args).count())
8007        .unwrap_or(0)
8008}
8009
8010pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
8011    let recovered_block_arguments = recovered_block_literal_arguments(node);
8012    (0..node.child_count())
8013        .filter_map(move |index| node.child(index))
8014        .filter(|child| child.is_named() && !child.is_extra())
8015        .flat_map(move |child| {
8016            if let Some((raw, left, right)) = recovered_block_arguments
8017                && child == raw
8018            {
8019                [Some(left), Some(right)]
8020            } else {
8021                [Some(child), None]
8022            }
8023        })
8024        .flatten()
8025}
8026
8027fn recovered_c_keyword_argument_count(
8028    file: &ProjectFile,
8029    call: Node<'_>,
8030    arguments: Node<'_>,
8031    source: &str,
8032) -> usize {
8033    // A C identifier that is a C++ keyword can be displaced twice by the C++
8034    // grammar: first into a direct parameter-list `ERROR(keyword)`, then into
8035    // a direct argument-list `ERROR(',', keyword)`. Match those CST tokens in
8036    // the enclosing C function before restoring the otherwise dropped slot.
8037    if !is_c_source_file(file) || arguments.kind() != "argument_list" {
8038        return 0;
8039    }
8040    let mut ancestor = Some(call);
8041    let function = loop {
8042        let Some(current) = ancestor else {
8043            return 0;
8044        };
8045        if current.kind() == "function_definition" {
8046            break current;
8047        }
8048        ancestor = current.parent();
8049    };
8050    let Some(parameters) = function
8051        .child_by_field_name("declarator")
8052        .and_then(|declarator| declarator.child_by_field_name("parameters"))
8053    else {
8054        return 0;
8055    };
8056    let displaced_parameter_keywords = (0..parameters.child_count())
8057        .filter_map(|index| parameters.child(index))
8058        .filter(|error| error.kind() == "ERROR")
8059        .filter_map(|error| {
8060            let parameter = error.prev_named_sibling()?;
8061            if parameter.kind() != "parameter_declaration"
8062                || parameter.end_byte() != error.start_byte()
8063                || extract_variable_name(parameter, source).is_some()
8064            {
8065                return None;
8066            }
8067            let mut children = (0..error.child_count())
8068                .filter_map(|index| error.child(index))
8069                .filter(|child| !child.is_extra() && !child.is_missing());
8070            let keyword = children.next()?;
8071            (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
8072                .then_some(keyword)
8073        })
8074        .collect::<Vec<_>>();
8075    if displaced_parameter_keywords.is_empty() {
8076        return 0;
8077    }
8078
8079    (0..arguments.child_count())
8080        .filter_map(|index| arguments.child(index))
8081        .filter(|error| error.kind() == "ERROR" && error.is_extra())
8082        .filter(|error| {
8083            let mut children = (0..error.child_count())
8084                .filter_map(|index| error.child(index))
8085                .filter(|child| !child.is_extra() && !child.is_missing());
8086            let Some(comma) = children.next() else {
8087                return false;
8088            };
8089            let Some(keyword) = children.next() else {
8090                return false;
8091            };
8092            children.next().is_none()
8093                && comma.kind() == ","
8094                && !keyword.is_named()
8095                && keyword.child_count() == 0
8096                && displaced_parameter_keywords
8097                    .iter()
8098                    .any(|parameter| parameter.kind_id() == keyword.kind_id())
8099        })
8100        .count()
8101}
8102
8103fn recovered_block_literal_arguments<'tree>(
8104    arguments: Node<'tree>,
8105) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
8106    if arguments.kind() != "argument_list" {
8107        return None;
8108    }
8109    let mut raw_arguments = (0..arguments.child_count())
8110        .filter_map(|index| arguments.child(index))
8111        .filter(|child| child.is_named() && !child.is_extra());
8112    let raw = raw_arguments.next()?;
8113    if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
8114        return None;
8115    }
8116
8117    let left = raw.child_by_field_name("left")?;
8118    if left.is_missing() || left.start_byte() == left.end_byte() {
8119        return None;
8120    }
8121    let right = raw.child_by_field_name("right")?;
8122    if right.kind() != "compound_literal_expression"
8123        || right.is_missing()
8124        || right
8125            .child_by_field_name("type")
8126            .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
8127        || right
8128            .child_by_field_name("value")
8129            .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
8130    {
8131        return None;
8132    }
8133    let has_intervening_error = (0..raw.child_count())
8134        .filter_map(|index| raw.child(index))
8135        .any(|child| {
8136            child.kind() == "ERROR"
8137                && !child.is_missing()
8138                && child.start_byte() >= left.end_byte()
8139                && child.end_byte() <= right.start_byte()
8140        });
8141    has_intervening_error.then_some((raw, left, right))
8142}
8143
8144pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
8145    match node.kind() {
8146        "new_expression" => node
8147            .child_by_field_name("type")
8148            .or_else(|| node.named_child(0)),
8149        "compound_literal_expression" => node.child_by_field_name("type"),
8150        "call_expression" => node.child_by_field_name("function"),
8151        _ => None,
8152    }
8153}
8154
8155pub fn field_initializer_constructs_target(
8156    node: Node<'_>,
8157    ctx: &ScanCtx<'_>,
8158    owner: &CodeUnit,
8159) -> bool {
8160    // A qualified name in a constructor initializer denotes a base
8161    // subobject constructor (`namespace::Base(args)`), not a member field.  The
8162    // field-initializer grammar exposes the qualified name as one structured
8163    // `qualified_identifier`; resolve its owner through the same lexical type
8164    // machinery used for ordinary C++ type references before considering the
8165    // initializer a hit.  This keeps an unrelated `namespace::Other(...)`, a
8166    // qualified non-constructor member, and an unresolved owner out of the
8167    // target constructor's inverse usage set.
8168    if first_named_child_of_kind(node, "qualified_identifier").is_some() {
8169        return qualified_base_initializer_constructs_target(node, ctx, owner);
8170    }
8171    let Some(name) = node
8172        .child_by_field_name("name")
8173        .or_else(|| first_named_child_of_kind(node, "field_identifier"))
8174        .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
8175    else {
8176        return false;
8177    };
8178    let field_name = node_text(name, ctx.source);
8179    ctx.visibility
8180        .visible_identifier_candidates(ctx.file, field_name)
8181        .filter(|unit| unit.is_field() && unit.identifier() == field_name)
8182        .any(|unit| field_declares_type(unit, ctx, owner))
8183}
8184
8185fn qualified_base_initializer_constructs_target(
8186    node: Node<'_>,
8187    ctx: &ScanCtx<'_>,
8188    owner: &CodeUnit,
8189) -> bool {
8190    let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
8191        return false;
8192    };
8193    let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
8194        return false;
8195    };
8196    let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
8197        return false;
8198    };
8199    let resolves_target = |components: &[String]| {
8200        matches!(
8201            ctx.visibility.resolve_type_components_lexically_for_target(
8202                &ctx.analyzer,
8203                ctx.file,
8204                components,
8205                is_globally_qualified_cpp_name(qualified),
8206                &lexical_scope,
8207                owner,
8208            ),
8209            LexicalTypeResolution::Resolved { unit, .. }
8210                if same_visible_symbol(&unit, owner)
8211        )
8212    };
8213    if resolves_target(&components) {
8214        return true;
8215    }
8216
8217    // Some real-world code spells a base mem-initializer as
8218    // `Base::Base(args)`. In that structured path the final component repeats
8219    // the constructor name; resolve the preceding type path. The terminal
8220    // identity check prevents an arbitrary qualified member from taking this
8221    // route.
8222    components
8223        .last()
8224        .is_some_and(|terminal| terminal == owner.identifier())
8225        && resolves_target(&components[..components.len() - 1])
8226}
8227
8228fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
8229    unit.signature()
8230        .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
8231        || ctx
8232            .analyzer
8233            .get_source(unit, false)
8234            .is_some_and(|declaration| {
8235                field_declaration_type_matches(&declaration, unit, ctx, owner)
8236            })
8237}
8238
8239pub fn field_declared_binding(
8240    analyzer: &CppGraphSource<'_>,
8241    visibility: &VisibilityIndex<'_>,
8242    visible_from: &ProjectFile,
8243    field: &CodeUnit,
8244) -> Option<CppScanBinding> {
8245    let fact = visibility.field_declared_type_fact(analyzer, field)?;
8246    let normalized = normalize_field_type_text(&fact.type_text);
8247    let resolved = visibility.resolve_unique_canonical_type_for_declaration(
8248        analyzer,
8249        visible_from,
8250        field,
8251        &normalized,
8252    );
8253    let resolved = match (resolved, fact.template_arguments.as_deref()) {
8254        (Some(primary), Some(arguments)) => visibility
8255            .resolve_template_arguments(visible_from, primary, arguments)
8256            .ok(),
8257        (resolved, None) => resolved,
8258        (None, Some(_)) => None,
8259    };
8260    Some(CppScanBinding::from_type_name(
8261        normalized,
8262        resolved,
8263        fact.indirection,
8264    ))
8265}
8266
8267/// The one logical type the candidates name, or why they do not name one.
8268fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
8269    let Some(first) = candidates.first() else {
8270        return Err(TypeCandidateFailure::Unresolvable);
8271    };
8272    if candidates
8273        .iter()
8274        .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
8275    {
8276        Ok((*first).clone())
8277    } else {
8278        Err(TypeCandidateFailure::Ambiguous)
8279    }
8280}
8281
8282fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
8283    logical_type_candidate(candidates).ok()
8284}
8285
8286fn unique_type_candidate_preserving_alias(
8287    analyzer: &CppGraphSource<'_>,
8288    candidates: &[&CodeUnit],
8289) -> Option<CodeUnit> {
8290    let first = *candidates.first()?;
8291    if declared_type_alias(analyzer, first) {
8292        return candidates
8293            .iter()
8294            .all(|candidate| {
8295                declared_type_alias(analyzer, candidate)
8296                    && candidate.kind() == first.kind()
8297                    && candidate.fq_name() == first.fq_name()
8298                    && candidate.source() == first.source()
8299            })
8300            .then(|| first.clone());
8301    }
8302    candidates
8303        .iter()
8304        .all(|candidate| {
8305            !declared_type_alias(analyzer, candidate)
8306                && candidate.kind() == first.kind()
8307                && candidate.fq_name() == first.fq_name()
8308        })
8309        .then(|| first.clone())
8310}
8311
8312fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
8313    is_type_alias(unit)
8314        || analyzer
8315            .type_alias_provider()
8316            .is_some_and(|provider| provider.is_type_alias(unit))
8317}
8318
8319pub fn field_declared_type_binding(
8320    analyzer: &CppGraphSource<'_>,
8321    visibility: &VisibilityIndex<'_>,
8322    visible_from: &ProjectFile,
8323    field: &CodeUnit,
8324) -> Option<(String, Option<CodeUnit>, i32)> {
8325    let fact = visibility.field_declared_type_fact(analyzer, field)?;
8326    let normalized = normalize_field_type_text(&fact.type_text);
8327    let primary = visibility.resolve_unique_canonical_type_for_declaration(
8328        analyzer,
8329        visible_from,
8330        field,
8331        &normalized,
8332    );
8333    let resolved = match (primary, fact.template_arguments.as_deref()) {
8334        (Some(primary), Some(arguments)) => visibility
8335            .resolve_template_arguments(visible_from, primary, arguments)
8336            .ok(),
8337        (resolved, None) => resolved,
8338        (None, Some(_)) => None,
8339    };
8340    Some((normalized, resolved, fact.indirection))
8341}
8342
8343fn decode_field_declared_type_fact(
8344    analyzer: &CppGraphSource<'_>,
8345    field: &CodeUnit,
8346) -> Option<DeclaredFieldTypeFact> {
8347    let declaration = analyzer.get_source(field, false)?;
8348    let mut parser = Parser::new();
8349    parser
8350        .set_language(&tree_sitter_cpp::LANGUAGE.into())
8351        .ok()?;
8352    let tree = parser.parse(&declaration, None)?;
8353    let mut stack = vec![tree.root_node()];
8354    while let Some(node) = stack.pop() {
8355        if matches!(node.kind(), "declaration" | "field_declaration")
8356            && let Some(type_node) = node
8357                .child_by_field_name("type")
8358                .or_else(|| first_type_child(node))
8359            && let Some(indirection) =
8360                declared_name_indirection(node, type_node, field.identifier(), &declaration)
8361        {
8362            return Some(DeclaredFieldTypeFact {
8363                type_text: node_text(type_node, &declaration).to_string(),
8364                indirection,
8365                template_arguments: cpp_template_reference_arguments(type_node, &declaration),
8366            });
8367        }
8368        let mut cursor = node.walk();
8369        stack.extend(node.named_children(&mut cursor));
8370    }
8371    None
8372}
8373
8374/// Text of the type that a C or C++ alias declaration names, read from the
8375/// `type_definition` or `alias_declaration` node's `type` field.
8376///
8377/// The declaration text is never scanned. A function-pointer typedef
8378/// interleaves its aliased type with its declarator (`typedef R (*F)(int)`),
8379/// so no prefix or suffix of the spelling isolates the target.
8380///
8381/// An alias whose declarator is a function declarator names a function type:
8382/// `typedef R F(int)`, `typedef R (*F)(int)`, `typedef R *F(int)`, and
8383/// `using F = R (*)(int)`. The analyzer's type model names declared types only,
8384/// so such an alias has no canonical target. Its `type` field holds the return
8385/// type `R`, which is a different type from the alias, so this returns `None`
8386/// rather than that return type.
8387pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
8388    let mut parser = Parser::new();
8389    parser
8390        .set_language(&tree_sitter_cpp::LANGUAGE.into())
8391        .ok()?;
8392    let tree = parser.parse(declaration, None)?;
8393    let mut stack = vec![tree.root_node()];
8394    while let Some(node) = stack.pop() {
8395        let type_node = match node.kind() {
8396            "type_definition" => {
8397                let mut cursor = node.walk();
8398                if node
8399                    .children_by_field_name("declarator", &mut cursor)
8400                    .any(declarator_names_function_type)
8401                {
8402                    return None;
8403                }
8404                node.child_by_field_name("type")?
8405            }
8406            "alias_declaration" => {
8407                let type_node = node.child_by_field_name("type")?;
8408                if type_node
8409                    .child_by_field_name("declarator")
8410                    .is_some_and(declarator_names_function_type)
8411                {
8412                    return None;
8413                }
8414                type_node
8415            }
8416            _ => {
8417                let mut cursor = node.walk();
8418                let children = node.named_children(&mut cursor).collect::<Vec<_>>();
8419                stack.extend(children.into_iter().rev());
8420                continue;
8421            }
8422        };
8423        return Some(node_text(type_node, declaration).to_string());
8424    }
8425    None
8426}
8427
8428/// True when an alias declarator names a function type.
8429///
8430/// The declarator chain is walked through the `declarator` field, so the
8431/// parameter list -- a sibling field -- is never entered and a parameter's own
8432/// function declarator cannot be mistaken for the alias's.
8433fn declarator_names_function_type(declarator: Node<'_>) -> bool {
8434    let mut current = Some(declarator);
8435    while let Some(node) = current {
8436        match node.kind() {
8437            "function_declarator" | "abstract_function_declarator" => return true,
8438            "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
8439                current = node.named_child(0);
8440            }
8441            _ => current = node.child_by_field_name("declarator"),
8442        }
8443    }
8444    false
8445}
8446
8447fn decode_structured_alias_target(
8448    analyzer: &CppGraphSource<'_>,
8449    unit: &CodeUnit,
8450) -> Option<StructuredAliasTarget> {
8451    analyzer
8452        .get_source(unit, false)
8453        .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
8454        .or_else(|| {
8455            let signature = unit.signature()?;
8456            decode_structured_alias_target_source(unit, signature, false)
8457        })
8458}
8459
8460fn decode_structured_alias_target_source(
8461    unit: &CodeUnit,
8462    declaration: &str,
8463    require_top_level: bool,
8464) -> Option<StructuredAliasTarget> {
8465    let mut parser = Parser::new();
8466    parser
8467        .set_language(&tree_sitter_cpp::LANGUAGE.into())
8468        .ok()?;
8469    let tree = parser.parse(declaration, None)?;
8470    let mut stack = vec![tree.root_node()];
8471    while let Some(node) = stack.pop() {
8472        let type_node = match node.kind() {
8473            "type_definition" => {
8474                if require_top_level
8475                    && node
8476                        .parent()
8477                        .is_none_or(|parent| parent.kind() != "translation_unit")
8478                {
8479                    let mut cursor = node.walk();
8480                    stack.extend(node.named_children(&mut cursor));
8481                    continue;
8482                }
8483                let mut declarator_cursor = node.walk();
8484                let declarator = node
8485                    .children_by_field_name("declarator", &mut declarator_cursor)
8486                    .find(|declarator| {
8487                        extract_typedef_declarator_name(*declarator, declaration)
8488                            .is_some_and(|name| name == unit.identifier())
8489                    })?;
8490                if declarator_names_function_type(declarator) {
8491                    return None;
8492                }
8493                node.child_by_field_name("type")?
8494            }
8495            "alias_declaration" => {
8496                if require_top_level
8497                    && node
8498                        .parent()
8499                        .is_none_or(|parent| parent.kind() != "translation_unit")
8500                {
8501                    let mut cursor = node.walk();
8502                    stack.extend(node.named_children(&mut cursor));
8503                    continue;
8504                }
8505                let name = node.child_by_field_name("name")?;
8506                if node_text(name, declaration) != unit.identifier() {
8507                    return None;
8508                }
8509                let type_node = node.child_by_field_name("type")?;
8510                if type_node
8511                    .child_by_field_name("declarator")
8512                    .is_some_and(declarator_names_function_type)
8513                {
8514                    return None;
8515                }
8516                type_node
8517            }
8518            _ => {
8519                let mut cursor = node.walk();
8520                stack.extend(node.named_children(&mut cursor));
8521                continue;
8522            }
8523        };
8524        return structured_alias_type_target(type_node, declaration);
8525    }
8526    None
8527}
8528
8529fn structured_alias_type_target(
8530    mut type_node: Node<'_>,
8531    source: &str,
8532) -> Option<StructuredAliasTarget> {
8533    while type_node.kind() == "type_descriptor" {
8534        type_node = type_node.child_by_field_name("type")?;
8535    }
8536    if type_node.kind() == "primitive_type" {
8537        return Some(StructuredAliasTarget::Builtin);
8538    }
8539    if matches!(
8540        type_node.kind(),
8541        "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
8542    ) {
8543        type_node = type_node.child_by_field_name("name")?;
8544    }
8545    let global = type_node.child_by_field_name("scope").is_none()
8546        && type_node.child(0).is_some_and(|child| child.kind() == "::");
8547    let mut components = Vec::new();
8548    append_structured_type_components(type_node, source, &mut components)?;
8549    let arguments = cpp_template_reference_arguments(type_node, source);
8550    (!components.is_empty()).then_some(StructuredAliasTarget::Named {
8551        components,
8552        global,
8553        arguments,
8554    })
8555}
8556
8557fn append_structured_type_components(
8558    node: Node<'_>,
8559    source: &str,
8560    out: &mut Vec<String>,
8561) -> Option<()> {
8562    match node.kind() {
8563        "identifier" | "namespace_identifier" | "type_identifier" => {
8564            out.push(node_text(node, source).to_string());
8565            Some(())
8566        }
8567        "template_type" => {
8568            append_structured_type_components(node.child_by_field_name("name")?, source, out)
8569        }
8570        "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
8571            if let Some(scope) = node.child_by_field_name("scope") {
8572                append_structured_type_components(scope, source, out)?;
8573            }
8574            append_structured_type_components(node.child_by_field_name("name")?, source, out)
8575        }
8576        _ => None,
8577    }
8578}
8579
8580fn declared_name_indirection(
8581    declaration: Node<'_>,
8582    type_node: Node<'_>,
8583    field_name: &str,
8584    source: &str,
8585) -> Option<i32> {
8586    let mut stack = Vec::new();
8587    let mut cursor = declaration.walk();
8588    stack.extend(
8589        declaration
8590            .named_children(&mut cursor)
8591            .filter(|child| !same_node(*child, type_node)),
8592    );
8593    while let Some(node) = stack.pop() {
8594        if matches!(node.kind(), "identifier" | "field_identifier")
8595            && node_text(node, source) == field_name
8596        {
8597            let mut indirection = 0;
8598            let mut current = node.parent();
8599            while let Some(parent) = current {
8600                if same_node(parent, declaration) {
8601                    return Some(indirection);
8602                }
8603                if parent.kind() == "pointer_declarator" {
8604                    indirection += 1;
8605                }
8606                current = parent.parent();
8607            }
8608            return None;
8609        }
8610        let mut cursor = node.walk();
8611        stack.extend(node.named_children(&mut cursor));
8612    }
8613    None
8614}
8615
8616fn field_declaration_type_matches(
8617    declaration: &str,
8618    unit: &CodeUnit,
8619    ctx: &ScanCtx<'_>,
8620    owner: &CodeUnit,
8621) -> bool {
8622    ctx.visibility
8623        .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
8624        || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
8625            let normalized = normalize_field_type_text(type_text);
8626            ctx.visibility
8627                .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
8628                || ctx.visibility.resolves_to_type(
8629                    &ctx.analyzer,
8630                    ctx.file,
8631                    normalized.as_str(),
8632                    owner,
8633                )
8634        })
8635}
8636
8637fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
8638    let declaration = declaration
8639        .split(['=', ';'])
8640        .next()
8641        .unwrap_or(declaration)
8642        .trim();
8643    let index = declaration.rfind(field_name)?;
8644    let before = &declaration[..index];
8645    let after = &declaration[index + field_name.len()..];
8646    if before.chars().next_back().is_some_and(is_identifier_char)
8647        || after.chars().next().is_some_and(is_identifier_char)
8648    {
8649        return None;
8650    }
8651    Some(before.trim())
8652}
8653
8654fn normalize_field_type_text(type_text: &str) -> String {
8655    const FIELD_SPECIFIERS: [&str; 8] = [
8656        "extern ",
8657        "static ",
8658        "mutable ",
8659        "constexpr ",
8660        "constinit ",
8661        "inline ",
8662        "volatile ",
8663        "const ",
8664    ];
8665
8666    let mut normalized = normalize_type_text(type_text);
8667    loop {
8668        let Some(stripped) = FIELD_SPECIFIERS
8669            .iter()
8670            .find_map(|specifier| normalized.strip_prefix(specifier))
8671        else {
8672            return normalized;
8673        };
8674        normalized = normalize_type_text(stripped);
8675    }
8676}
8677
8678fn is_identifier_char(ch: char) -> bool {
8679    ch == '_' || ch.is_ascii_alphanumeric()
8680}
8681
8682pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
8683    let Some(type_node) = node.child_by_field_name("type") else {
8684        return false;
8685    };
8686    ctx.visibility.resolves_to_type(
8687        &ctx.analyzer,
8688        ctx.file,
8689        node_text(type_node, ctx.source),
8690        owner,
8691    )
8692}
8693
8694pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8695    !ctx.analyzer
8696        .declarations(ctx.file)
8697        .into_iter()
8698        .filter(|unit| unit.is_function())
8699        .any(|unit| {
8700            ctx.analyzer.ranges(&unit).iter().any(|range| {
8701                node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
8702            })
8703        })
8704}
8705
8706pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
8707    let mut cursor = node.walk();
8708    for child in node.named_children(&mut cursor) {
8709        if child.kind() == "init_declarator" {
8710            return child
8711                .child_by_field_name("value")
8712                .or_else(|| first_named_child_of_kind(child, "initializer_list"))
8713                .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
8714                .map(declaration_init_value_arity)
8715                .unwrap_or(0);
8716        }
8717        if is_declarator_node(child) {
8718            return declaration_declarator_arity(child);
8719        }
8720    }
8721    0
8722}
8723
8724fn declaration_init_value_arity(value: Node<'_>) -> usize {
8725    match value.kind() {
8726        "argument_list" | "initializer_list" => argument_children(value).count(),
8727        "compound_literal_expression" => call_arity(value),
8728        _ => 1,
8729    }
8730}
8731
8732fn declaration_declarator_arity(node: Node<'_>) -> usize {
8733    if let Some(parameters) = node.child_by_field_name("parameters") {
8734        return argument_children(parameters).count();
8735    }
8736    node.child_by_field_name("declarator")
8737        .map(declaration_declarator_arity)
8738        .unwrap_or(0)
8739}
8740
8741fn first_named_child_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
8742    let mut cursor = node.walk();
8743    node.named_children(&mut cursor)
8744        .find(|child| child.kind() == kind)
8745}
8746
8747fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
8748    let mut stack = vec![root];
8749    while let Some(node) = stack.pop() {
8750        if node.kind() == kind {
8751            return Some(node);
8752        }
8753        for index in (0..node.named_child_count()).rev() {
8754            if let Some(child) = node.named_child(index) {
8755                stack.push(child);
8756            }
8757        }
8758    }
8759    None
8760}
8761
8762fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
8763    if node.kind() == "identifier" {
8764        return true;
8765    }
8766    if node.kind() == "parenthesized_expression" {
8767        return false;
8768    }
8769    if node.kind() == "call_expression" {
8770        return node
8771            .child_by_field_name("function")
8772            .is_some_and(|function| function.kind() == "identifier");
8773    }
8774    let mut stack = vec![node];
8775    while let Some(descendant) = stack.pop() {
8776        if descendant != node && descendant.kind() == "parenthesized_expression" {
8777            continue;
8778        }
8779        if descendant.kind() == "identifier" {
8780            return true;
8781        }
8782        if descendant.kind() == "call_expression" {
8783            if descendant
8784                .child_by_field_name("function")
8785                .is_some_and(|function| function.kind() == "identifier")
8786            {
8787                return true;
8788            }
8789            continue;
8790        }
8791        for index in (0..descendant.named_child_count()).rev() {
8792            if let Some(child) = descendant.named_child(index) {
8793                stack.push(child);
8794            }
8795        }
8796    }
8797    false
8798}
8799
8800fn macro_expansion_shape_is_safe(
8801    node: Node<'_>,
8802    source: &str,
8803    parameters: &[String],
8804    environment: &MacroEnvironment,
8805) -> bool {
8806    if matches!(node.kind(), "identifier" | "parenthesized_expression") {
8807        return true;
8808    }
8809    if node.kind() == "call_expression" {
8810        let Some(function) = node.child_by_field_name("function") else {
8811            return true;
8812        };
8813        if function.kind() != "identifier" {
8814            return true;
8815        }
8816        let function_name = node_text(function, source);
8817        if parameters
8818            .iter()
8819            .any(|parameter| parameter == function_name)
8820        {
8821            return false;
8822        }
8823        if !environment.may_bind(function_name) {
8824            return true;
8825        }
8826        let Some(arguments) = node.child_by_field_name("arguments") else {
8827            return false;
8828        };
8829        return argument_children(arguments).all(|argument| {
8830            if argument.kind() == "identifier"
8831                && parameters
8832                    .iter()
8833                    .any(|parameter| parameter == node_text(argument, source))
8834            {
8835                return false;
8836            }
8837            macro_expansion_shape_is_safe(argument, source, parameters, environment)
8838        });
8839    }
8840    let mut stack = vec![node];
8841    while let Some(descendant) = stack.pop() {
8842        if descendant != node {
8843            if descendant.kind() == "parenthesized_expression" {
8844                continue;
8845            }
8846            if descendant.kind() == "call_expression" {
8847                let expands = descendant
8848                    .child_by_field_name("function")
8849                    .filter(|function| function.kind() == "identifier")
8850                    .is_some_and(|function| environment.may_bind(node_text(function, source)));
8851                if expands {
8852                    return false;
8853                }
8854                continue;
8855            }
8856        }
8857        if descendant.kind() == "identifier" {
8858            let identifier = node_text(descendant, source);
8859            if parameters.iter().any(|parameter| parameter == identifier)
8860                || environment.may_bind(identifier)
8861            {
8862                return false;
8863            }
8864        }
8865        for index in (0..descendant.named_child_count()).rev() {
8866            if let Some(child) = descendant.named_child(index) {
8867                stack.push(child);
8868            }
8869        }
8870    }
8871    true
8872}
8873
8874fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
8875    let text = node_text(path, source);
8876    match path.kind() {
8877        "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
8878        "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
8879        _ => None,
8880    }
8881}
8882
8883fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
8884    let descendant = node;
8885    while let Some(parent) = node.parent() {
8886        if is_preprocessor_conditional(parent)
8887            && !is_file_covering_include_guard(parent, source)
8888            && preprocessor_conditional_contains_descendant(parent, descendant)
8889        {
8890            return true;
8891        }
8892        node = parent;
8893    }
8894    false
8895}
8896
8897fn is_preprocessor_conditional(node: Node<'_>) -> bool {
8898    matches!(
8899        node.kind(),
8900        "preproc_if"
8901            | "preproc_ifdef"
8902            | "preproc_ifndef"
8903            | "preproc_elif"
8904            | "preproc_elifdef"
8905            | "preproc_else"
8906    )
8907}
8908
8909fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
8910    node.parent()
8911        .filter(|parent| parent.kind() == "translation_unit")
8912        .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
8913        && is_canonical_include_guard(node, source)
8914}
8915
8916fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
8917    if node.kind() != "preproc_ifdef"
8918        || node
8919            .child(0)
8920            .is_none_or(|directive| directive.kind() != "#ifndef")
8921        || node.child_by_field_name("alternative").is_some()
8922    {
8923        return false;
8924    }
8925    let Some(guard_name) = node.child_by_field_name("name") else {
8926        return false;
8927    };
8928    let mut cursor = node.walk();
8929    node.named_children(&mut cursor)
8930        .find(|child| *child != guard_name && child.kind() != "comment")
8931        .filter(|child| child.kind() == "preproc_def")
8932        .and_then(|definition| definition.child_by_field_name("name"))
8933        .is_some_and(|defined_name| {
8934            node_text(defined_name, source) == node_text(guard_name, source)
8935        })
8936}
8937
8938fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
8939    let mut guard = None;
8940    for index in 0..root.named_child_count() {
8941        let Some(child) = root.named_child(index) else {
8942            continue;
8943        };
8944        if child.kind() == "comment" || is_pragma_once(child, source) {
8945            continue;
8946        }
8947        if guard.is_none() && is_canonical_include_guard(child, source) {
8948            guard = Some(child);
8949        } else {
8950            return None;
8951        }
8952    }
8953    guard
8954        .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
8955        .map(|name| node_text(name, source).to_string())
8956}
8957
8958fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
8959    if (0..root.named_child_count())
8960        .filter_map(|index| root.named_child(index))
8961        .any(|child| is_pragma_once(child, source))
8962    {
8963        return MacroIncludeProtection::PragmaOnce;
8964    }
8965    top_level_canonical_include_guard_name(root, source)
8966        .map(MacroIncludeProtection::MacroGuard)
8967        .unwrap_or(MacroIncludeProtection::None)
8968}
8969
8970fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
8971    node.kind() == "preproc_call"
8972        && node
8973            .child_by_field_name("directive")
8974            .is_some_and(|directive| node_text(directive, source) == "#pragma")
8975        && node
8976            .child_by_field_name("argument")
8977            .is_some_and(|argument| node_text(argument, source).trim() == "once")
8978}
8979
8980fn parse_preproc_identifier(argument: &str) -> Option<String> {
8981    let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
8982    let mut parser = Parser::new();
8983    parser
8984        .set_language(&tree_sitter_cpp::LANGUAGE.into())
8985        .ok()?;
8986    let tree = parser.parse(&sentinel, None)?;
8987    if tree.root_node().has_error() {
8988        return None;
8989    }
8990    let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
8991    let identifier = statement.named_child(0)?;
8992    (identifier.kind() == "identifier" && statement.named_child_count() == 1)
8993        .then(|| node_text(identifier, &sentinel).to_string())
8994}
8995
8996pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
8997    match node.kind() {
8998        "identifier" | "field_identifier" => {
8999            let name = node_text(node, source).trim();
9000            (!name.is_empty()).then(|| name.to_string())
9001        }
9002        "abstract_array_declarator"
9003        | "abstract_function_declarator"
9004        | "abstract_parenthesized_declarator"
9005        | "abstract_pointer_declarator"
9006        | "abstract_reference_declarator" => None,
9007        "function_declarator" => node
9008            .child_by_field_name("declarator")
9009            .or_else(|| node.child_by_field_name("name"))
9010            .and_then(|child| extract_variable_name(child, source)),
9011        _ => node
9012            .child_by_field_name("declarator")
9013            .or_else(|| node.child_by_field_name("name"))
9014            .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
9015            .and_then(|child| extract_variable_name(child, source)),
9016    }
9017}
9018
9019/// Whether `file` is proven to use plain-C source semantics.
9020///
9021/// `Language::Cpp` intentionally serves both C and C++. Headers do not carry a
9022/// compilation dialect on their own, so only an exact `.c` source extension is
9023/// sufficient to reinterpret C++-grammar keyword nodes such as `this` as C
9024/// identifiers.
9025pub fn is_c_source_file(file: &ProjectFile) -> bool {
9026    file.rel_path()
9027        .extension()
9028        .and_then(|extension| extension.to_str())
9029        == Some("c")
9030}
9031
9032pub fn is_declarator_node(node: Node<'_>) -> bool {
9033    matches!(
9034        node.kind(),
9035        "identifier"
9036            | "field_identifier"
9037            | "pointer_declarator"
9038            | "reference_declarator"
9039            | "array_declarator"
9040            | "parenthesized_declarator"
9041            | "function_declarator"
9042    )
9043}
9044
9045#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9046pub enum RecoveredDeclaratorTypeContext {
9047    Declaration,
9048    FunctionDefinition,
9049    Parameter,
9050}
9051
9052/// Recognize a real type displaced into a qualified declarator by parser
9053/// recovery.
9054///
9055/// Tree-sitter parses `API Result *make(Arg);` as if `API` were the declared
9056/// type and `Result` were the scope of a qualified declarator with a missing
9057/// `::`. A template return such as `API Result<T> make()` uses a
9058/// `template_type` for the same recovered scope. The same recovery occurs for
9059/// macro-prefixed definitions, extern variables, and macro-decorated
9060/// parameters (`f(MACRO T* p)`, where the parameter's own `type` field takes
9061/// the macro). Keep this intentionally structural: the recovered scope must
9062/// have the grammar's missing separator, the qualified node must occupy the
9063/// declaration's declarator chain, a separate nonempty type must occupy the
9064/// normal type field, and the recovered name must unwrap to a real declarator
9065/// name.
9066pub fn recovered_macro_decorated_declarator_type(
9067    node: Node<'_>,
9068) -> Option<RecoveredDeclaratorTypeContext> {
9069    recovered_macro_decorated_type_node(node).map(|(_, context)| context)
9070}
9071
9072/// Return the declaration/function `type` displaced by a macro-shaped
9073/// qualified declarator, together with the enclosing declaration context.
9074/// Callers use the macro scope only as structural admission evidence; the
9075/// returned node is the real type reference to resolve and record.
9076pub fn recovered_macro_decorated_type_node(
9077    node: Node<'_>,
9078) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
9079    if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
9080        return None;
9081    }
9082    let qualified = node.parent()?;
9083    if qualified.kind() != "qualified_identifier"
9084        || qualified.child_by_field_name("scope") != Some(node)
9085        || !(0..qualified.child_count())
9086            .filter_map(|index| qualified.child(index))
9087            .any(|child| child.kind() == "::" && child.is_missing())
9088    {
9089        return None;
9090    }
9091    if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
9092        return None;
9093    }
9094
9095    let (declaration, context) = recovered_declarator_container(qualified)?;
9096    let type_node = declaration
9097        .child_by_field_name("type")
9098        .filter(|type_node| {
9099            *type_node != qualified
9100                && !type_node.is_missing()
9101                && type_node.start_byte() != type_node.end_byte()
9102        })?;
9103    Some((type_node, context))
9104}
9105
9106fn recovered_declarator_container(
9107    mut declarator: Node<'_>,
9108) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
9109    loop {
9110        let parent = declarator.parent()?;
9111        if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
9112            return Some((
9113                parent
9114                    .parent()
9115                    .filter(|declaration| declaration.kind() == "declaration")?,
9116                RecoveredDeclaratorTypeContext::Declaration,
9117            ));
9118        }
9119        if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
9120            return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
9121        }
9122        if parent.kind() == "function_definition"
9123            && has_field_child(parent, "declarator", declarator)
9124        {
9125            return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
9126        }
9127        // `f(MACRO T* p)` recovers exactly like `MACRO T *make(...)` does, one
9128        // level down: the parameter's `type` field takes the macro token and
9129        // the real type `T` becomes the recovered scope of the declarator.
9130        // Declining here left every xxhash `XXH_NOESCAPE` parameter with no
9131        // candidate at all (#1830).
9132        if matches!(
9133            parent.kind(),
9134            "parameter_declaration" | "optional_parameter_declaration"
9135        ) && has_field_child(parent, "declarator", declarator)
9136        {
9137            return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
9138        }
9139        if !matches!(
9140            parent.kind(),
9141            "array_declarator"
9142                | "function_declarator"
9143                | "parenthesized_declarator"
9144                | "pointer_declarator"
9145                | "pointer_type_declarator"
9146                | "reference_declarator"
9147        ) || !has_field_child(parent, "declarator", declarator)
9148        {
9149            return None;
9150        }
9151        declarator = parent;
9152    }
9153}
9154
9155fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
9156    let mut cursor = parent.walk();
9157    parent
9158        .children_by_field_name(field, &mut cursor)
9159        .any(|child| child == target)
9160}
9161
9162fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
9163    loop {
9164        if node.is_missing() || node.start_byte() == node.end_byte() {
9165            return false;
9166        }
9167        match node.kind() {
9168            "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
9169                return true;
9170            }
9171            "array_declarator"
9172            | "function_declarator"
9173            | "parenthesized_declarator"
9174            | "pointer_declarator"
9175            | "pointer_type_declarator"
9176            | "reference_declarator" => {
9177                let Some(declarator) = node.child_by_field_name("declarator") else {
9178                    return false;
9179                };
9180                node = declarator;
9181            }
9182            _ => return false,
9183        }
9184    }
9185}
9186
9187/// Aggregate-owner proof for a structurally recognized designated initializer.
9188pub enum DesignatedInitializerOwner {
9189    Resolved(CodeUnit),
9190    Unresolved,
9191}
9192
9193/// Recognize a designated-initializer field and, when possible, resolve its
9194/// aggregate owner.
9195///
9196/// Covers both the grammar's ordinary `field_designator` shape and the exact
9197/// recovery used for `.field = value` after a preprocessor-split array
9198/// initializer. Nested aggregate levels are deliberately left unresolved unless
9199/// the single outer level is the containing array initializer: resolving those
9200/// would require following the enclosing field's declared type. `None` means the
9201/// node is not a designator at all; an unresolved designator remains classified so
9202/// callers cannot fall through to unrelated global/member heuristics.
9203pub fn designated_initializer_owner(
9204    visibility: &VisibilityIndex<'_>,
9205    file: &ProjectFile,
9206    source: &str,
9207    node: Node<'_>,
9208) -> Option<DesignatedInitializerOwner> {
9209    if let Some(designator) = node
9210        .parent()
9211        .filter(|parent| parent.kind() == "field_designator")
9212    {
9213        let pair = designator.parent()?;
9214        if pair.kind() != "initializer_pair"
9215            || pair.child_by_field_name("designator") != Some(designator)
9216        {
9217            return None;
9218        }
9219        let initializer = pair.parent()?;
9220        if initializer.kind() != "initializer_list" {
9221            return None;
9222        }
9223        return Some(classified_designated_owner(initializer_list_owner(
9224            visibility,
9225            file,
9226            source,
9227            initializer,
9228        )));
9229    }
9230
9231    let init_declarator = node.parent()?;
9232    if init_declarator.child_by_field_name("declarator") != Some(node)
9233        || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
9234    {
9235        return None;
9236    }
9237    Some(classified_designated_owner(declaration_owner(
9238        visibility,
9239        file,
9240        source,
9241        init_declarator.parent()?,
9242    )))
9243}
9244
9245fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
9246    owner.map_or(
9247        DesignatedInitializerOwner::Unresolved,
9248        DesignatedInitializerOwner::Resolved,
9249    )
9250}
9251
9252fn initializer_list_owner(
9253    visibility: &VisibilityIndex<'_>,
9254    file: &ProjectFile,
9255    source: &str,
9256    initializer: Node<'_>,
9257) -> Option<CodeUnit> {
9258    let mut current = initializer;
9259    let mut outer_initializer_lists = 0usize;
9260    loop {
9261        let parent = current.parent()?;
9262        match parent.kind() {
9263            "initializer_pair" => return None,
9264            "initializer_list" => {
9265                outer_initializer_lists += 1;
9266                if outer_initializer_lists > 1 {
9267                    return None;
9268                }
9269                current = parent;
9270            }
9271            "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
9272                let declaration = parent.parent()?;
9273                if outer_initializer_lists == 1
9274                    && !parent
9275                        .child_by_field_name("declarator")
9276                        .is_some_and(contains_array_declarator)
9277                {
9278                    return None;
9279                }
9280                return declaration_owner(visibility, file, source, declaration);
9281            }
9282            "compound_literal_expression"
9283                if parent.child_by_field_name("value") == Some(current)
9284                    && outer_initializer_lists == 0 =>
9285            {
9286                let type_node = parent.child_by_field_name("type")?;
9287                return resolve_designated_owner_type(visibility, file, source, type_node);
9288            }
9289            "ERROR" => current = parent,
9290            _ => return None,
9291        }
9292    }
9293}
9294
9295fn declaration_owner(
9296    visibility: &VisibilityIndex<'_>,
9297    file: &ProjectFile,
9298    source: &str,
9299    declaration: Node<'_>,
9300) -> Option<CodeUnit> {
9301    if !matches!(declaration.kind(), "declaration" | "field_declaration") {
9302        return None;
9303    }
9304    let type_node = declaration
9305        .child_by_field_name("type")
9306        .or_else(|| first_type_child(declaration))?;
9307    resolve_designated_owner_type(visibility, file, source, type_node)
9308}
9309
9310fn resolve_designated_owner_type(
9311    visibility: &VisibilityIndex<'_>,
9312    file: &ProjectFile,
9313    source: &str,
9314    type_node: Node<'_>,
9315) -> Option<CodeUnit> {
9316    let type_name = normalize_type_text(node_text(type_node, source));
9317    visibility
9318        .resolve_type(file, &type_name)
9319        .filter(CodeUnit::is_class)
9320}
9321
9322fn contains_array_declarator(declarator: Node<'_>) -> bool {
9323    let mut stack = vec![declarator];
9324    while let Some(node) = stack.pop() {
9325        if node.kind() == "array_declarator" {
9326            return true;
9327        }
9328        if matches!(node.kind(), "initializer_list" | "compound_statement") {
9329            continue;
9330        }
9331        let mut cursor = node.walk();
9332        stack.extend(node.named_children(&mut cursor));
9333    }
9334    false
9335}
9336
9337pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
9338    let mut cursor = node.walk();
9339    node.named_children(&mut cursor).find(|child| {
9340        matches!(
9341            child.kind(),
9342            "type_identifier"
9343                | "primitive_type"
9344                | "qualified_identifier"
9345                | "scoped_type_identifier"
9346                | "struct_specifier"
9347                | "union_specifier"
9348                | "enum_specifier"
9349        )
9350    })
9351}
9352
9353pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
9354    visibility: &VisibilityIndex<'_>,
9355    file: &ProjectFile,
9356    source: &str,
9357    declarator: Node<'_>,
9358    type_text: Option<&str>,
9359    bindings: &LocalInferenceEngine<T>,
9360) -> bool {
9361    if !has_ancestor_kind(declarator, "compound_statement") {
9362        return false;
9363    }
9364    if declarator
9365        .child_by_field_name("declarator")
9366        .is_none_or(|declarator| declarator.kind() != "identifier")
9367    {
9368        return false;
9369    }
9370    if !type_text
9371        .and_then(|text| visibility.resolve_type(file, text))
9372        .is_some_and(|unit| unit.is_class())
9373    {
9374        return false;
9375    }
9376    declarator
9377        .child_by_field_name("parameters")
9378        .is_some_and(|parameters| {
9379            constructor_parameters_look_like_expressions(parameters, source, bindings)
9380        })
9381}
9382
9383fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
9384    parameters: Node<'_>,
9385    source: &str,
9386    bindings: &LocalInferenceEngine<T>,
9387) -> bool {
9388    let mut cursor = parameters.walk();
9389    parameters.named_children(&mut cursor).any(|parameter| {
9390        !matches!(
9391            parameter.kind(),
9392            "parameter_declaration" | "optional_parameter_declaration"
9393        ) || parameter_declaration_is_local_expression(parameter, source, bindings)
9394    })
9395}
9396
9397fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
9398    parameter: Node<'_>,
9399    source: &str,
9400    bindings: &LocalInferenceEngine<T>,
9401) -> bool {
9402    let text = node_text(parameter, source).trim();
9403    text.chars()
9404        .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
9405        && bindings.is_shadowed(text)
9406}
9407
9408pub fn is_declaration_name(node: Node<'_>) -> bool {
9409    let Some(parent) = node.parent() else {
9410        return false;
9411    };
9412    if parent
9413        .child_by_field_name("name")
9414        .is_some_and(|name| same_node(name, node))
9415    {
9416        if matches!(
9417            parent.kind(),
9418            "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
9419        ) {
9420            return cpp_tag_specifier_declares_name(parent);
9421        }
9422        if matches!(
9423            parent.kind(),
9424            "namespace_definition"
9425                | "namespace_alias_definition"
9426                | "alias_declaration"
9427                | "enumerator"
9428        ) {
9429            return true;
9430        }
9431    }
9432
9433    let mut current = Some(parent);
9434    while let Some(ancestor) = current {
9435        let type_definition = ancestor.kind() == "type_definition";
9436        let mut declarator_cursor = ancestor.walk();
9437        if ancestor
9438            .children_by_field_name("declarator", &mut declarator_cursor)
9439            .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
9440        {
9441            return true;
9442        }
9443        if matches!(
9444            ancestor.kind(),
9445            "declaration"
9446                | "field_declaration"
9447                | "parameter_declaration"
9448                | "optional_parameter_declaration"
9449                | "function_definition"
9450                | "type_definition"
9451                | "alias_declaration"
9452                | "class_specifier"
9453                | "struct_specifier"
9454                | "union_specifier"
9455                | "enum_specifier"
9456        ) {
9457            return false;
9458        }
9459        current = ancestor.parent();
9460    }
9461    false
9462}
9463
9464pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
9465    if !matches!(node.kind(), "identifier" | "field_identifier") || is_declaration_name(node) {
9466        return false;
9467    }
9468    if let Some(parent) = node.parent() {
9469        if parent.kind() == "call_expression"
9470            && parent.child_by_field_name("function") == Some(node)
9471        {
9472            return false;
9473        }
9474        if matches!(parent.kind(), "labeled_statement" | "goto_statement")
9475            && parent.child_by_field_name("label") == Some(node)
9476        {
9477            return false;
9478        }
9479    }
9480    let mut current = node.parent();
9481    while let Some(ancestor) = current {
9482        if ancestor.kind().starts_with("preproc_") {
9483            return false;
9484        }
9485        if matches!(
9486            ancestor.kind(),
9487            "translation_unit" | "function_definition" | "compound_statement"
9488        ) {
9489            break;
9490        }
9491        current = ancestor.parent();
9492    }
9493    true
9494}
9495
9496fn recovered_c_reference_node(
9497    visibility: &VisibilityIndex<'_>,
9498    file: &ProjectFile,
9499    node: Node<'_>,
9500    source: &str,
9501) -> bool {
9502    if node.start_byte() >= node.end_byte()
9503        || node.is_error()
9504        || node.is_missing()
9505        || !matches!(
9506            node.kind(),
9507            "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
9508        )
9509        || recovered_c_macro_binding_role(node)
9510        || recovered_c_label_role(node)
9511    {
9512        return false;
9513    }
9514
9515    let name = node_text(node, source);
9516    if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
9517        return true;
9518    }
9519    if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
9520        return true;
9521    }
9522    if is_declaration_name(node) {
9523        return false;
9524    }
9525    if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
9526        return true;
9527    }
9528    recovered_c_reference_anchor(node)
9529}
9530
9531fn recovered_c_explicit_assignment_callee(
9532    visibility: &VisibilityIndex<'_>,
9533    file: &ProjectFile,
9534    node: Node<'_>,
9535    name: &str,
9536) -> bool {
9537    let mut current = node;
9538    let error = loop {
9539        let Some(parent) = current.parent() else {
9540            return false;
9541        };
9542        if parent.is_error() {
9543            break parent;
9544        }
9545        current = parent;
9546    };
9547    let mut cursor = error.walk();
9548    let explicit_recovery_precedes_callee = error
9549        .named_children(&mut cursor)
9550        .take_while(|child| child.start_byte() < node.start_byte())
9551        .any(|child| child.kind() == "explicit_function_specifier");
9552    if !explicit_recovery_precedes_callee {
9553        return false;
9554    }
9555    visibility
9556        .cpp
9557        .declarations(file)
9558        .iter()
9559        .chain(visibility.visible_by_file.get(file).into_iter().flatten())
9560        .any(|candidate| candidate.identifier() == name && candidate.is_function())
9561}
9562
9563fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
9564    while let Some(parent) = node.parent() {
9565        if matches!(
9566            parent.kind(),
9567            "preproc_def" | "preproc_function_def" | "preproc_params"
9568        ) {
9569            return true;
9570        }
9571        if parent.is_error()
9572            || matches!(
9573                parent.kind(),
9574                "translation_unit" | "function_definition" | "compound_statement"
9575            )
9576        {
9577            return false;
9578        }
9579        node = parent;
9580    }
9581    false
9582}
9583
9584fn recovered_c_label_role(node: Node<'_>) -> bool {
9585    node.parent().is_some_and(|parent| {
9586        matches!(parent.kind(), "labeled_statement" | "goto_statement")
9587            && parent.child_by_field_name("label") == Some(node)
9588    })
9589}
9590
9591fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
9592    while let Some(parent) = node.parent() {
9593        if parent.is_error() {
9594            return false;
9595        }
9596        if parent.kind().ends_with("_expression")
9597            || matches!(
9598                parent.kind(),
9599                "argument_list"
9600                    | "return_statement"
9601                    | "expression_statement"
9602                    | "case_statement"
9603                    | "initializer_list"
9604                    | "init_declarator"
9605                    | "array_declarator"
9606                    | "field_designator"
9607                    | "enumerator"
9608            )
9609        {
9610            return true;
9611        }
9612        if matches!(
9613            parent.kind(),
9614            "translation_unit"
9615                | "function_definition"
9616                | "compound_statement"
9617                | "declaration"
9618                | "field_declaration"
9619                | "parameter_declaration"
9620        ) {
9621            return false;
9622        }
9623        node = parent;
9624    }
9625    false
9626}
9627
9628/// Whether a parameter declaration belongs to the callable scope whose body can
9629/// contain references to it.
9630///
9631/// Error recovery can wrap a macro-decorated class body in a synthetic outer
9632/// `function_definition`. Merely finding any callable ancestor would then leak
9633/// parameters from member prototypes into later member bodies. Require the
9634/// parameter to be inside that definition's own declarator instead.
9635pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
9636    let mut current = parameter.parent();
9637    while let Some(ancestor) = current {
9638        if ancestor.kind() == "lambda_expression" {
9639            return ancestor
9640                .child_by_field_name("declarator")
9641                .is_some_and(|declarator| {
9642                    declarator.start_byte() <= parameter.start_byte()
9643                        && parameter.end_byte() <= declarator.end_byte()
9644                });
9645        }
9646        if ancestor.kind() == "function_definition" {
9647            return ancestor
9648                .child_by_field_name("declarator")
9649                .is_some_and(|declarator| {
9650                    declarator.start_byte() <= parameter.start_byte()
9651                        && parameter.end_byte() <= declarator.end_byte()
9652                });
9653        }
9654        current = ancestor.parent();
9655    }
9656    false
9657}
9658
9659fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
9660    if specifier.child_by_field_name("body").is_some() {
9661        return true;
9662    }
9663    let mut current = specifier.parent();
9664    while let Some(ancestor) = current {
9665        match ancestor.kind() {
9666            "type_descriptor"
9667            | "parameter_declaration"
9668            | "optional_parameter_declaration"
9669            | "template_argument_list"
9670            | "cast_expression" => return false,
9671            "declaration" | "field_declaration" => {
9672                let mut cursor = ancestor.walk();
9673                return ancestor
9674                    .children_by_field_name("declarator", &mut cursor)
9675                    .next()
9676                    .is_none();
9677            }
9678            "translation_unit" => return true,
9679            _ => current = ancestor.parent(),
9680        }
9681    }
9682    false
9683}
9684
9685pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
9686    match node.kind() {
9687        "identifier"
9688        | "field_identifier"
9689        | "qualified_identifier"
9690        | "scoped_identifier"
9691        | "operator_name"
9692        | "destructor_name"
9693        | "literal_operator_name" => Some(node),
9694        _ => node
9695            .child_by_field_name("declarator")
9696            .or_else(|| node.child_by_field_name("name"))
9697            .or_else(|| node.child_by_field_name("field"))
9698            .and_then(declarator_name_node),
9699    }
9700}
9701
9702fn declarator_name_path_contains(
9703    declarator: Node<'_>,
9704    candidate: Node<'_>,
9705    allow_type_identifier: bool,
9706) -> bool {
9707    let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
9708        return false;
9709    };
9710    let mut current = Some(declarator);
9711    while let Some(node) = current {
9712        if same_node(node, candidate) {
9713            return true;
9714        }
9715        if same_node(node, name) {
9716            return false;
9717        }
9718        current = node
9719            .child_by_field_name("declarator")
9720            .or_else(|| node.child_by_field_name("name"))
9721            .or_else(|| node.child_by_field_name("field"));
9722    }
9723    false
9724}
9725
9726fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
9727    match node.kind() {
9728        "identifier"
9729        | "field_identifier"
9730        | "operator_name"
9731        | "destructor_name"
9732        | "literal_operator_name" => Some(node),
9733        "type_identifier" if allow_type_identifier => Some(node),
9734        _ => node
9735            .child_by_field_name("declarator")
9736            .or_else(|| node.child_by_field_name("name"))
9737            .or_else(|| node.child_by_field_name("field"))
9738            .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
9739    }
9740}
9741
9742/// True when `node` is a component of a larger structured type node whose outer
9743/// range is the single reference surfaced to callers.
9744pub fn is_nested_type_node(node: Node<'_>) -> bool {
9745    node.parent().is_some_and(|parent| {
9746        matches!(
9747            parent.kind(),
9748            "qualified_identifier" | "scoped_type_identifier" | "template_type"
9749        )
9750    })
9751}
9752
9753pub struct OutOfLineMemberDefinitionOwners<'tree> {
9754    pub owners: Vec<(Node<'tree>, CodeUnit)>,
9755    innermost: Option<(Node<'tree>, CodeUnit)>,
9756}
9757
9758impl OutOfLineMemberDefinitionOwners<'_> {
9759    pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
9760        self.innermost.as_ref().map(|(node, owner)| (*node, owner))
9761    }
9762}
9763
9764pub struct QualifiedOwnerComponents<'tree> {
9765    pub nodes: Vec<Node<'tree>>,
9766    pub names: Vec<String>,
9767    pub global: bool,
9768}
9769
9770/// True when each structured qualifier on the callable-name path has a real
9771/// `::` token. A macro-prefixed return type can make tree-sitter insert a
9772/// zero-width missing separator and parse `TYPE Result<T> method()` as the
9773/// false qualified declarator `Result<T>::method`.
9774pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
9775    let mut stack = vec![node];
9776    let mut found_separator = false;
9777    while let Some(current) = stack.pop() {
9778        if !matches!(
9779            current.kind(),
9780            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
9781        ) {
9782            continue;
9783        }
9784        let mut current_has_separator = false;
9785        for index in 0..current.child_count() {
9786            let Some(child) = current.child(index) else {
9787                continue;
9788            };
9789            if child.kind() == "::" {
9790                if child.is_missing() {
9791                    return false;
9792                }
9793                current_has_separator = true;
9794                found_separator = true;
9795            }
9796        }
9797        if !current_has_separator {
9798            return false;
9799        }
9800        for field in ["scope", "name"] {
9801            if let Some(child) = current.child_by_field_name(field)
9802                && matches!(
9803                    child.kind(),
9804                    "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
9805                )
9806            {
9807                stack.push(child);
9808            }
9809        }
9810    }
9811    found_separator
9812}
9813
9814pub fn qualified_owner_components<'tree>(
9815    node: Node<'tree>,
9816    source: &str,
9817) -> Option<QualifiedOwnerComponents<'tree>> {
9818    if !qualified_name_has_concrete_scope_separators(node) {
9819        return None;
9820    }
9821    let mut nodes = cpp_name_component_nodes(node)?;
9822    nodes.pop()?;
9823    if nodes.is_empty() {
9824        return None;
9825    }
9826    let names = nodes
9827        .iter()
9828        .map(|component| node_text(*component, source).to_string())
9829        .collect();
9830    Some(QualifiedOwnerComponents {
9831        nodes,
9832        names,
9833        global: is_globally_qualified_cpp_name(node),
9834    })
9835}
9836
9837/// Return the terminal type-name occurrence in an out-of-line destructor
9838/// declarator such as `endpoint::~endpoint`.  Unlike an ordinary terminal
9839/// method name, this identifier is a second reference to the owner type.
9840///
9841/// Every extra qualifier nests another `qualified_identifier` in the `name`
9842/// field, so `zmq::pair_t::~pair_t` reaches the destructor only two levels
9843/// down. Reading one level dropped the terminal occurrence for every
9844/// file-scope out-of-line member libzmq writes (#1831).
9845pub fn out_of_line_destructor_type_reference(node: Node<'_>) -> Option<Node<'_>> {
9846    if node.kind() != "qualified_identifier" {
9847        return None;
9848    }
9849    let mut qualified = node;
9850    let destructor = loop {
9851        let name = qualified.child_by_field_name("name")?;
9852        match name.kind() {
9853            "qualified_identifier" => qualified = name,
9854            "destructor_name" => break name,
9855            _ => return None,
9856        }
9857    };
9858    (0..destructor.named_child_count())
9859        .filter_map(|index| destructor.named_child(index))
9860        .find(|child| matches!(child.kind(), "identifier" | "type_identifier"))
9861}
9862
9863pub fn out_of_line_member_definition_owner<'tree>(
9864    analyzer: &CppGraphSource<'_>,
9865    visibility: &VisibilityIndex<'_>,
9866    file: &ProjectFile,
9867    source: &str,
9868    node: Node<'tree>,
9869) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
9870    if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
9871        || !has_ancestor_kind(node, "function_definition")
9872        || !is_function_declarator_name_root(node)
9873    {
9874        return None;
9875    }
9876    let qualified = qualified_owner_components(node, source)?;
9877    let lexical_scope = enclosing_namespace_components(node, source)?;
9878    let mut owners = Vec::new();
9879    let mut innermost = None;
9880
9881    for component_count in 1..=qualified.names.len() {
9882        if let LexicalTypeResolution::Resolved { unit, .. } = visibility
9883            .resolve_type_components_lexically(
9884                analyzer,
9885                file,
9886                &qualified.names[..component_count],
9887                qualified.global,
9888                &lexical_scope,
9889            )
9890            && !owners
9891                .iter()
9892                .any(|(_, existing)| same_visible_symbol(existing, &unit))
9893        {
9894            if component_count == qualified.names.len() {
9895                innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
9896            }
9897            owners.push((qualified.nodes[component_count - 1], unit));
9898        }
9899    }
9900
9901    // The C++ analyzer has already reconciled an indexed out-of-line callable
9902    // against the include-visible class table. Consult that canonical owner
9903    // chain only when ordinary lexical lookup could not recover the innermost
9904    // owner.  A one-segment qualifier is safe here only when the enclosing
9905    // indexed callable has an authoritative class owner and the parser's
9906    // namespace path is a (possibly sparse) subsequence of that owner path.
9907    // The latter is what lets macro-wrapped namespace sentinels recover a
9908    // missing `time_internal`/`cord_internal` component without guessing an
9909    // unrelated short name.
9910    if innermost.is_none() {
9911        let indexed_owner_components = visibility
9912            .indexed_enclosing_owner_scope(analyzer, file, node)
9913            .or_else(|| {
9914                // Retain the legacy rendered-name fallback for the existing
9915                // multi-segment path when an enclosing owner chain is not
9916                // available (for example, cache-loaded units without parent
9917                // links).  One-segment recovery must stay canonical-only.
9918                if qualified.names.len() <= 1 {
9919                    return None;
9920                }
9921                let range = Range {
9922                    start_byte: node.start_byte(),
9923                    end_byte: node.end_byte(),
9924                    start_line: node.start_position().row,
9925                    end_line: node.end_position().row,
9926                };
9927                let start = analyzer.enclosing_code_unit(file, &range)?;
9928                let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9929                    brokk_bifrost_core::analyzer::Language::Cpp,
9930                    &cpp_name_for(&start),
9931                );
9932                components.pop();
9933                Some(components)
9934            });
9935        if let Some(indexed_owner_components) = indexed_owner_components
9936            && indexed_owner_components.len() > qualified.names.len()
9937            && indexed_owner_components.ends_with(&qualified.names)
9938            && indexed_namespace_path_is_recoverable(
9939                &lexical_scope,
9940                &indexed_owner_components,
9941            )
9942            // A globally-qualified one-segment owner is an explicit request
9943            // for the top-level binding; do not reinterpret it as a missing
9944            // namespace component.  Existing multi-segment global lookups
9945            // retain their historical indexed recovery.
9946            && (qualified.names.len() > 1 || !qualified.global)
9947        {
9948            let namespace_count = indexed_owner_components.len() - qualified.names.len();
9949            for component_count in 1..=qualified.names.len() {
9950                let expected = &indexed_owner_components[..namespace_count + component_count];
9951                let owner_node = qualified.nodes[component_count - 1];
9952                for owner in visibility
9953                    .visible_identifier_candidates(file, &qualified.names[component_count - 1])
9954                    .filter(|candidate| candidate.is_class())
9955                    .filter(|candidate| {
9956                        canonical_cpp_scope_components(candidate) == expected
9957                            && visibility.external_type_candidate_visible_in_context(
9958                                analyzer, file, candidate, node,
9959                            )
9960                    })
9961                {
9962                    if component_count == qualified.names.len() && innermost.is_none() {
9963                        innermost = Some((owner_node, owner.clone()));
9964                    }
9965                    if !owners
9966                        .iter()
9967                        .any(|(_, existing)| same_symbol(existing, owner))
9968                    {
9969                        owners.push((owner_node, owner.clone()));
9970                    }
9971                }
9972            }
9973        }
9974    }
9975    (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
9976}
9977
9978fn is_function_declarator_name_root(node: Node<'_>) -> bool {
9979    let mut current = node;
9980    while let Some(parent) = current.parent() {
9981        if parent.kind() == "function_declarator" {
9982            return parent.child_by_field_name("declarator") == Some(current);
9983        }
9984        if matches!(
9985            parent.kind(),
9986            "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
9987        ) && parent.child_by_field_name("declarator") == Some(current)
9988        {
9989            current = parent;
9990            continue;
9991        }
9992        return false;
9993    }
9994    false
9995}
9996
9997pub fn append_cpp_name_components(
9998    node: Node<'_>,
9999    source: &str,
10000    out: &mut Vec<String>,
10001) -> Option<()> {
10002    out.extend(
10003        cpp_name_component_nodes(node)?
10004            .into_iter()
10005            .map(|component| node_text(component, source).to_string()),
10006    );
10007    Some(())
10008}
10009
10010pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
10011    let mut components = Vec::new();
10012    append_cpp_name_components(node, source, &mut components)?;
10013    Some(components)
10014}
10015
10016pub fn cpp_template_reference_arguments(
10017    mut node: Node<'_>,
10018    source: &str,
10019) -> Option<Vec<CppTemplateExpression>> {
10020    loop {
10021        match node.kind() {
10022            "template_type" | "template_function" => {
10023                let arguments = node.child_by_field_name("arguments")?;
10024                let mut cursor = arguments.walk();
10025                return Some(
10026                    arguments
10027                        .named_children(&mut cursor)
10028                        .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
10029                        .map(|argument| CppTemplateExpression {
10030                            text: normalize_cpp_whitespace(node_text(argument, source)),
10031                            term: cpp_template_term(argument, source, &[]),
10032                        })
10033                        .collect(),
10034                );
10035            }
10036            "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
10037                node = node
10038                    .child_by_field_name("name")
10039                    .or_else(|| node.child_by_field_name("type"))?;
10040            }
10041            _ => return None,
10042        }
10043    }
10044}
10045
10046fn cpp_reconcile_primary_template_parameters(
10047    candidates: &[(&CodeUnit, &CppTemplateMetadata)],
10048    preferred: &CodeUnit,
10049) -> Option<Vec<CppTemplateParameterMetadata>> {
10050    let canonical = candidates
10051        .iter()
10052        .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
10053    let mut merged = canonical
10054        .parameters
10055        .iter()
10056        .map(|parameter| CppTemplateParameterMetadata {
10057            name: parameter.name.clone(),
10058            kind: parameter.kind,
10059            variadic: parameter.variadic,
10060            default: None,
10061        })
10062        .collect::<Vec<_>>();
10063
10064    for (_, metadata) in candidates {
10065        if metadata.parameters.len() != merged.len() {
10066            return None;
10067        }
10068        let rename_bindings = metadata
10069            .parameters
10070            .iter()
10071            .zip(&merged)
10072            .map(|(parameter, canonical)| {
10073                (
10074                    parameter.name.clone(),
10075                    CppTemplateTerm::Parameter(canonical.name.clone()),
10076                )
10077            })
10078            .collect::<HashMap<_, _>>();
10079        for ((parameter, canonical), merged_parameter) in metadata
10080            .parameters
10081            .iter()
10082            .zip(&canonical.parameters)
10083            .zip(&mut merged)
10084        {
10085            if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
10086                return None;
10087            }
10088            let Some(default) = &parameter.default else {
10089                continue;
10090            };
10091            let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
10092            if let Some(existing) = &merged_parameter.default {
10093                if !cpp_template_terms_equal(&existing.term, &normalized_term) {
10094                    return None;
10095                }
10096            } else {
10097                merged_parameter.default = Some(CppTemplateExpression {
10098                    text: default.text.clone(),
10099                    term: normalized_term,
10100                });
10101            }
10102        }
10103    }
10104    Some(merged)
10105}
10106
10107pub fn cpp_bind_template_arguments(
10108    parameters: &[CppTemplateParameterMetadata],
10109    explicit_arguments: &[CppTemplateExpression],
10110) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
10111    let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
10112    if variadic_index.is_some_and(|index| {
10113        index + 1 != parameters.len()
10114            || parameters[index + 1..]
10115                .iter()
10116                .any(|parameter| parameter.variadic)
10117    }) {
10118        return None;
10119    }
10120    let fixed_count = variadic_index.unwrap_or(parameters.len());
10121    if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
10122        return None;
10123    }
10124    let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
10125    let mut expanded = explicit_arguments[..explicit_fixed_count]
10126        .iter()
10127        .map(cpp_clone_template_expression_iterative)
10128        .collect::<Vec<_>>();
10129    let mut bindings = HashMap::default();
10130    for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
10131        bindings.insert(
10132            parameter.name.clone(),
10133            cpp_clone_template_term_iterative(&argument.term),
10134        );
10135    }
10136    for parameter in &parameters[explicit_fixed_count..fixed_count] {
10137        let default = parameter.default.as_ref()?;
10138        let term = cpp_substitute_template_term(&default.term, &bindings)?;
10139        bindings.insert(parameter.name.clone(), term.clone());
10140        expanded.push(CppTemplateExpression {
10141            text: default.text.clone(),
10142            term,
10143        });
10144    }
10145    if let Some(index) = variadic_index {
10146        let packed_arguments = &explicit_arguments[explicit_fixed_count..];
10147        expanded.extend(
10148            packed_arguments
10149                .iter()
10150                .map(cpp_clone_template_expression_iterative),
10151        );
10152        bindings.insert(
10153            parameters[index].name.clone(),
10154            CppTemplateTerm::Node {
10155                kind: "parameter_pack".to_string(),
10156                children: packed_arguments
10157                    .iter()
10158                    .map(|argument| cpp_clone_template_term_iterative(&argument.term))
10159                    .collect(),
10160            },
10161        );
10162    }
10163    Some((expanded, bindings))
10164}
10165
10166fn cpp_specialization_matches(
10167    metadata: &CppTemplateMetadata,
10168    arguments: &[CppTemplateExpression],
10169) -> bool {
10170    if metadata.specialization_arguments.len() != arguments.len() {
10171        return false;
10172    }
10173    let parameter_names = metadata
10174        .parameters
10175        .iter()
10176        .map(|parameter| parameter.name.as_str())
10177        .collect::<HashSet<_>>();
10178    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
10179    for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
10180        if !cpp_unify_template_term(
10181            &pattern.term,
10182            &argument.term,
10183            &parameter_names,
10184            &mut bindings,
10185        ) {
10186            return false;
10187        }
10188    }
10189    true
10190}
10191
10192fn cpp_specialization_more_specialized(
10193    candidate: &CppTemplateMetadata,
10194    other: &CppTemplateMetadata,
10195) -> bool {
10196    cpp_specialization_pattern_accepts(other, candidate)
10197        && !cpp_specialization_pattern_accepts(candidate, other)
10198}
10199
10200fn cpp_specialization_pattern_accepts(
10201    broader: &CppTemplateMetadata,
10202    narrower: &CppTemplateMetadata,
10203) -> bool {
10204    if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
10205        return false;
10206    }
10207    let parameter_names = broader
10208        .parameters
10209        .iter()
10210        .map(|parameter| parameter.name.as_str())
10211        .collect::<HashSet<_>>();
10212    let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
10213    broader
10214        .specialization_arguments
10215        .iter()
10216        .zip(&narrower.specialization_arguments)
10217        .all(|(pattern, argument)| {
10218            cpp_unify_template_term(
10219                &pattern.term,
10220                &argument.term,
10221                &parameter_names,
10222                &mut bindings,
10223            )
10224        })
10225}
10226
10227pub fn cpp_substitute_template_term(
10228    term: &CppTemplateTerm,
10229    bindings: &HashMap<String, CppTemplateTerm>,
10230) -> Option<CppTemplateTerm> {
10231    enum Work<'a> {
10232        Visit(&'a CppTemplateTerm),
10233        Build { kind: String, child_count: usize },
10234    }
10235
10236    let mut work = vec![Work::Visit(term)];
10237    let mut substituted = Vec::new();
10238    while let Some(next) = work.pop() {
10239        match next {
10240            Work::Visit(CppTemplateTerm::Parameter(name)) => {
10241                substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
10242            }
10243            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
10244                substituted.push(CppTemplateTerm::Atom {
10245                    kind: kind.clone(),
10246                    text: text.clone(),
10247                });
10248            }
10249            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
10250                work.push(Work::Build {
10251                    kind: kind.clone(),
10252                    child_count: children.len(),
10253                });
10254                work.extend(children.iter().rev().map(Work::Visit));
10255            }
10256            Work::Build { kind, child_count } => {
10257                let children = substituted.split_off(substituted.len() - child_count);
10258                substituted.push(CppTemplateTerm::Node { kind, children });
10259            }
10260        }
10261    }
10262    substituted.pop()
10263}
10264
10265pub fn cpp_substitute_template_arguments(
10266    arguments: &[CppTemplateExpression],
10267    bindings: &HashMap<String, CppTemplateTerm>,
10268) -> Option<Vec<CppTemplateExpression>> {
10269    let mut substituted = Vec::new();
10270    for argument in arguments {
10271        let CppTemplateTerm::Node { kind, children } = &argument.term else {
10272            substituted.push(CppTemplateExpression {
10273                text: argument.text.clone(),
10274                term: cpp_substitute_template_term(&argument.term, bindings)?,
10275            });
10276            continue;
10277        };
10278        if kind != "parameter_pack_expansion" {
10279            substituted.push(CppTemplateExpression {
10280                text: argument.text.clone(),
10281                term: cpp_substitute_template_term(&argument.term, bindings)?,
10282            });
10283            continue;
10284        }
10285        let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
10286            return None;
10287        };
10288        if ellipsis != "..." {
10289            return None;
10290        }
10291
10292        let mut pack_names = Vec::new();
10293        let mut work = vec![pattern];
10294        while let Some(term) = work.pop() {
10295            match term {
10296                CppTemplateTerm::Parameter(name)
10297                    if matches!(
10298                        bindings.get(name),
10299                        Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
10300                    ) =>
10301                {
10302                    if !pack_names.contains(name) {
10303                        pack_names.push(name.clone());
10304                    }
10305                }
10306                CppTemplateTerm::Node { children, .. } => work.extend(children),
10307                CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
10308            }
10309        }
10310        let first_pack = pack_names.first()?;
10311        let CppTemplateTerm::Node {
10312            children: first_elements,
10313            ..
10314        } = bindings.get(first_pack)?
10315        else {
10316            return None;
10317        };
10318        let pack_len = first_elements.len();
10319        for pack_name in &pack_names {
10320            let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
10321                return None;
10322            };
10323            if children.len() != pack_len {
10324                return None;
10325            }
10326        }
10327        for index in 0..pack_len {
10328            let mut element_bindings = bindings.clone();
10329            for pack_name in &pack_names {
10330                let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
10331                    return None;
10332                };
10333                element_bindings.insert(
10334                    pack_name.clone(),
10335                    cpp_clone_template_term_iterative(&children[index]),
10336                );
10337            }
10338            substituted.push(CppTemplateExpression {
10339                text: argument.text.clone(),
10340                term: cpp_substitute_template_term(pattern, &element_bindings)?,
10341            });
10342        }
10343    }
10344    Some(substituted)
10345}
10346
10347fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
10348    enum Work<'a> {
10349        Visit(&'a CppTemplateTerm),
10350        Build { kind: String, child_count: usize },
10351    }
10352
10353    let mut work = vec![Work::Visit(term)];
10354    let mut cloned = Vec::new();
10355    while let Some(next) = work.pop() {
10356        match next {
10357            Work::Visit(CppTemplateTerm::Parameter(name)) => {
10358                cloned.push(CppTemplateTerm::Parameter(name.clone()));
10359            }
10360            Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
10361                cloned.push(CppTemplateTerm::Atom {
10362                    kind: kind.clone(),
10363                    text: text.clone(),
10364                });
10365            }
10366            Work::Visit(CppTemplateTerm::Node { kind, children }) => {
10367                work.push(Work::Build {
10368                    kind: kind.clone(),
10369                    child_count: children.len(),
10370                });
10371                work.extend(children.iter().rev().map(Work::Visit));
10372            }
10373            Work::Build { kind, child_count } => {
10374                let children = cloned.split_off(cloned.len() - child_count);
10375                cloned.push(CppTemplateTerm::Node { kind, children });
10376            }
10377        }
10378    }
10379    cloned
10380        .pop()
10381        .expect("template term traversal emits one root")
10382}
10383
10384fn cpp_clone_template_expression_iterative(
10385    expression: &CppTemplateExpression,
10386) -> CppTemplateExpression {
10387    CppTemplateExpression {
10388        text: expression.text.clone(),
10389        term: cpp_clone_template_term_iterative(&expression.term),
10390    }
10391}
10392
10393pub fn cpp_unify_template_term(
10394    pattern: &CppTemplateTerm,
10395    argument: &CppTemplateTerm,
10396    parameters: &HashSet<&str>,
10397    bindings: &mut HashMap<String, CppTemplateTerm>,
10398) -> bool {
10399    let mut work = vec![(pattern, argument)];
10400    while let Some((pattern, argument)) = work.pop() {
10401        match pattern {
10402            CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
10403                if let Some(bound) = bindings.get(name) {
10404                    if !cpp_template_terms_equal(bound, argument) {
10405                        return false;
10406                    }
10407                } else {
10408                    bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
10409                }
10410            }
10411            CppTemplateTerm::Atom {
10412                kind: pattern_kind,
10413                text: pattern_text,
10414            } => {
10415                if !matches!(
10416                    argument,
10417                    CppTemplateTerm::Atom { kind, text }
10418                        if kind == pattern_kind && text == pattern_text
10419                ) {
10420                    return false;
10421                }
10422            }
10423            CppTemplateTerm::Node {
10424                kind: pattern_kind,
10425                children: pattern_children,
10426            } => {
10427                let CppTemplateTerm::Node { kind, children } = argument else {
10428                    return false;
10429                };
10430                if kind != pattern_kind || children.len() != pattern_children.len() {
10431                    return false;
10432                }
10433                work.extend(pattern_children.iter().zip(children).rev());
10434            }
10435            CppTemplateTerm::Parameter(_) => return false,
10436        }
10437    }
10438    true
10439}
10440
10441fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
10442    let mut work = vec![(left, right)];
10443    while let Some((left, right)) = work.pop() {
10444        match (left, right) {
10445            (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
10446                if left != right {
10447                    return false;
10448                }
10449            }
10450            (
10451                CppTemplateTerm::Atom {
10452                    kind: left_kind,
10453                    text: left_text,
10454                },
10455                CppTemplateTerm::Atom {
10456                    kind: right_kind,
10457                    text: right_text,
10458                },
10459            ) => {
10460                if left_kind != right_kind || left_text != right_text {
10461                    return false;
10462                }
10463            }
10464            (
10465                CppTemplateTerm::Node {
10466                    kind: left_kind,
10467                    children: left_children,
10468                },
10469                CppTemplateTerm::Node {
10470                    kind: right_kind,
10471                    children: right_children,
10472                },
10473            ) => {
10474                if left_kind != right_kind || left_children.len() != right_children.len() {
10475                    return false;
10476                }
10477                work.extend(left_children.iter().zip(right_children).rev());
10478            }
10479            _ => return false,
10480        }
10481    }
10482    true
10483}
10484
10485pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
10486    let mut components = Vec::new();
10487    let mut stack = vec![node];
10488    while let Some(current) = stack.pop() {
10489        match current.kind() {
10490            "identifier"
10491            | "field_identifier"
10492            | "namespace_identifier"
10493            | "type_identifier"
10494            | "operator_name"
10495            | "destructor_name" => components.push(current),
10496            "template_type" | "template_function" => {
10497                stack.push(current.child_by_field_name("name")?);
10498            }
10499            "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
10500                stack.push(current.child_by_field_name("name")?);
10501                if let Some(scope) = current.child_by_field_name("scope") {
10502                    stack.push(scope);
10503                }
10504            }
10505            "nested_namespace_specifier" => {
10506                for index in (0..current.named_child_count()).rev() {
10507                    stack.push(current.named_child(index)?);
10508                }
10509            }
10510            _ => return None,
10511        }
10512    }
10513    Some(components)
10514}
10515
10516pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
10517    node.child_by_field_name("scope").is_none()
10518        && node.child(0).is_some_and(|child| child.kind() == "::")
10519}
10520
10521fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
10522    let mut namespaces = Vec::new();
10523    let mut current = node.parent();
10524    while let Some(parent) = current {
10525        if parent.kind() == "namespace_definition"
10526            && let Some(name) = parent.child_by_field_name("name")
10527        {
10528            let mut components = Vec::new();
10529            append_cpp_name_components(name, source, &mut components)?;
10530            namespaces.push(components);
10531        }
10532        current = parent.parent();
10533    }
10534    namespaces.reverse();
10535    Some(namespaces.into_iter().flatten().collect())
10536}
10537
10538/// Whether a parser-derived namespace path can be reconciled with an indexed
10539/// owner scope without inventing an unrelated short-name binding.
10540///
10541/// Macro namespace sentinels can make tree-sitter omit one or more namespace
10542/// definitions from the ancestor chain.  Preserve the order of every
10543/// namespace that did survive parsing, but allow indexed components between
10544/// them.  An empty path is deliberately rejected: a one-segment owner at the
10545/// translation-unit root is not evidence of a malformed namespace.
10546fn indexed_namespace_path_is_recoverable(
10547    lexical_scope: &[String],
10548    indexed_owner_scope: &[String],
10549) -> bool {
10550    if lexical_scope.is_empty() || lexical_scope.len() >= indexed_owner_scope.len() {
10551        return false;
10552    }
10553    let mut indexed = indexed_owner_scope.iter();
10554    lexical_scope
10555        .iter()
10556        .all(|component| indexed.any(|candidate| candidate == component))
10557}
10558
10559pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
10560    let mut current = node.parent();
10561    while let Some(parent) = current {
10562        if parent.kind() == kind {
10563            return true;
10564        }
10565        current = parent.parent();
10566    }
10567    false
10568}
10569
10570/// Return the terminal identifier represented by a callable or type callee.
10571///
10572/// Qualified, scoped, template, and field wrappers are traversed through their
10573/// grammar fields so both function calls and type constructions emit the token
10574/// that names the referenced declaration.
10575pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
10576    loop {
10577        let next = match node.kind() {
10578            "qualified_identifier"
10579            | "scoped_identifier"
10580            | "template_method"
10581            | "template_function"
10582            | "template_type" => node.child_by_field_name("name"),
10583            "field_expression" => node.child_by_field_name("field"),
10584            _ => None,
10585        };
10586        let Some(next) = next else {
10587            return node;
10588        };
10589        node = next;
10590    }
10591}
10592
10593/// Whether `node` is part of a call's callee expression, walking only through
10594/// the grammar wrappers that can structurally contain that callee.
10595pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
10596    while let Some(parent) = node.parent() {
10597        match parent.kind() {
10598            "call_expression" => {
10599                return parent
10600                    .child_by_field_name("function")
10601                    .or_else(|| parent.named_child(0))
10602                    == Some(node);
10603            }
10604            "qualified_identifier"
10605            | "scoped_identifier"
10606            | "template_function"
10607            | "template_type"
10608            | "field_expression" => node = parent,
10609            _ => return false,
10610        }
10611    }
10612    false
10613}
10614
10615pub fn type_reference_hit_node<'tree, T: Clone + Eq + Hash>(
10616    node: Node<'tree>,
10617    file: &ProjectFile,
10618    source: &str,
10619    bindings: &LocalInferenceEngine<T>,
10620) -> Node<'tree> {
10621    if is_call_callee_node(node) {
10622        return function_terminal_node(node);
10623    }
10624    if file.rel_path().extension().is_some_and(|ext| ext == "c") {
10625        return node;
10626    }
10627    let mut current = node;
10628    let declaration = loop {
10629        let Some(parent) = current.parent() else {
10630            return node;
10631        };
10632        if parent.kind() == "declaration" {
10633            break parent;
10634        }
10635        if matches!(
10636            parent.kind(),
10637            "compound_statement" | "function_definition" | "lambda_expression"
10638        ) {
10639            return node;
10640        }
10641        current = parent;
10642    };
10643    let Some(_type_node) = declaration.child_by_field_name("type").filter(|type_node| {
10644        type_node.start_byte() <= node.start_byte() && node.end_byte() <= type_node.end_byte()
10645    }) else {
10646        return node;
10647    };
10648    let mut cursor = declaration.walk();
10649    let constructs_object = declaration.named_children(&mut cursor).any(|child| {
10650        if child.kind() == "init_declarator" {
10651            return child.child_by_field_name("value").is_some()
10652                || first_named_child_of_kind(child, "initializer_list").is_some()
10653                || first_named_child_of_kind(child, "compound_literal_expression").is_some();
10654        }
10655        let declarator = if is_declarator_node(child) {
10656            Some(child)
10657        } else {
10658            None
10659        };
10660        declarator.is_some_and(|declarator| {
10661            declarator.kind() == "function_declarator"
10662                && has_ancestor_kind(declarator, "compound_statement")
10663                && declarator
10664                    .child_by_field_name("declarator")
10665                    .is_some_and(|name| name.kind() == "identifier")
10666                && declarator
10667                    .child_by_field_name("parameters")
10668                    .is_some_and(|parameters| {
10669                        constructor_parameters_look_like_expressions(parameters, source, bindings)
10670                    })
10671        })
10672    });
10673    if constructs_object {
10674        function_terminal_node(node)
10675    } else {
10676        node
10677    }
10678}
10679
10680pub fn normalize_type_text(value: &str) -> String {
10681    strip_tag_type_prefix(
10682        normalize_cpp_whitespace(value)
10683            .trim_start_matches("const ")
10684            .trim_end_matches('*')
10685            .trim_end_matches('&')
10686            .trim(),
10687    )
10688    .to_string()
10689}
10690
10691fn strip_tag_type_prefix(value: &str) -> &str {
10692    let value = value.trim_start_matches("const ");
10693    value
10694        .strip_prefix("struct ")
10695        .or_else(|| value.strip_prefix("class "))
10696        .or_else(|| value.strip_prefix("enum "))
10697        .unwrap_or(value)
10698        .trim()
10699}
10700
10701pub fn normalize_reference_name(value: &str) -> Option<String> {
10702    let normalized = normalize_cpp_reference_text(value);
10703    (!normalized.is_empty()).then_some(normalized)
10704}
10705
10706pub fn normalize_cpp_reference_text(value: &str) -> String {
10707    let mut text = normalize_cpp_whitespace(value)
10708        .trim_start_matches("new ")
10709        .trim()
10710        .to_string();
10711    if let Some(index) = text.find(['(', '{']) {
10712        text.truncate(index);
10713    }
10714    if let Some(index) = text.find('<') {
10715        text.truncate(index);
10716    }
10717    let normalized = text
10718        .trim()
10719        .trim_start_matches("const ")
10720        .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
10721        .trim_matches(':')
10722        .trim();
10723    strip_tag_type_prefix(normalized).to_string()
10724}
10725
10726pub fn cpp_name_for(unit: &CodeUnit) -> String {
10727    let short = unit.short_name().replace(['.', '$'], "::");
10728    if unit.package_name().is_empty() {
10729        short
10730    } else {
10731        format!("{}::{}", unit.package_name(), short)
10732    }
10733}
10734
10735/// Render an indexed C++ qualified name from its authoritative FqName
10736/// segments. Unlike the legacy `cpp_name_for` renderer, this preserves dots
10737/// that belong to a template argument (for example `Args...`).
10738fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
10739    let fq = unit.fq();
10740    if fq.is_empty() {
10741        return None;
10742    }
10743    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
10744    Some(
10745        fq.segments()
10746            .iter()
10747            .map(|&segment| interner.resolve(segment).0)
10748            .collect::<Vec<_>>()
10749            .join("::"),
10750    )
10751}
10752
10753fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
10754    canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
10755        || unit.fq().is_empty() && cpp_name_for(unit) == expected
10756}
10757
10758/// Return the indexed C++ owner scope without reparsing its rendered name.
10759///
10760/// Template spellings are opaque within an indexed `FqName` segment.  In
10761/// particular, the ellipsis in a parameter pack (`Args...`) is part of the
10762/// `AtomicHook<...>` type segment; feeding the legacy all-`::` rendering back
10763/// through `parse_symbol_path` would mistake those dots for component
10764/// separators.  Cache-loaded/legacy units may still have an empty structured
10765/// name, so retain the parser only as that explicit fallback.
10766pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
10767    let fq = unit.fq();
10768    if !fq.is_empty() {
10769        let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
10770        let scope = fq
10771            .segments()
10772            .iter()
10773            .filter_map(|&segment| {
10774                let (text, kind) = interner.resolve(segment);
10775                matches!(
10776                    kind,
10777                    brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
10778                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
10779                        | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
10780                )
10781                .then(|| text.to_string())
10782            })
10783            .collect();
10784        return scope;
10785    }
10786    brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10787        brokk_bifrost_core::analyzer::Language::Cpp,
10788        &cpp_name_for(unit),
10789    )
10790}
10791
10792// fqname-M4: the second stage splits on the individual chars '.', '-', '>'
10793// (not the substring "->"), which deliberately reduces an `operator->`-style
10794// terminal segment to an empty tail rather than keeping it intact; the shared
10795// structured splitter's cpp operator-token merge would keep `operator->`
10796// whole instead, changing this function's result — `name_matches_callable`'s
10797// `expected.starts_with("operator")` fallback exists specifically to
10798// compensate for that reduction, and a pinned regression test
10799// (`operator-> must not be reduced with terminal_name-style punctuation
10800// splitting`) asserts today's char-class behavior. Not equivalence-provable;
10801// revisit alongside that pinned test if it is ever relaxed.
10802pub fn terminal_name(value: &str) -> &str {
10803    value
10804        .rsplit("::")
10805        .next()
10806        .unwrap_or(value)
10807        .rsplit(['.', '-', '>'])
10808        .next()
10809        .unwrap_or(value)
10810        .trim()
10811}
10812
10813pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
10814    terminal_name(&normalize_cpp_reference_text(value)) == expected
10815}
10816
10817pub fn name_matches_callable(value: &str, expected: &str) -> bool {
10818    name_matches_terminal(value, expected)
10819        || expected.starts_with("operator")
10820            && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
10821}
10822
10823pub fn name_mentions(value: &str, expected: &str) -> bool {
10824    normalize_cpp_reference_text(value)
10825        .split("::")
10826        .any(|part| part == expected)
10827}
10828
10829pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
10830    let cpp_name = cpp_name_for(unit);
10831    if reference.contains("::") {
10832        return reference == cpp_name;
10833    }
10834    reference == cpp_name
10835        || terminal_name(reference) == unit.identifier()
10836            && (unit.package_name().is_empty() || reference == unit.identifier())
10837}
10838
10839pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
10840    match kind {
10841        TargetKind::Type
10842        | TargetKind::Constructor
10843        | TargetKind::Method
10844        | TargetKind::MemberField => true,
10845        TargetKind::FreeFunction => unit.is_function(),
10846        TargetKind::GlobalField => unit.is_field(),
10847        TargetKind::Macro => unit.is_macro(),
10848    }
10849}
10850
10851pub fn is_type_alias(unit: &CodeUnit) -> bool {
10852    unit.kind() == CodeUnitType::Field
10853        && unit.signature().is_some_and(|signature| {
10854            signature.starts_with("typedef ") || signature.starts_with("using ")
10855        })
10856}
10857
10858fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
10859    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
10860    let target_name = cpp_name_for(target);
10861    if normalized.contains("::") {
10862        return normalized == target_name;
10863    }
10864    if let Some(namespace) = alias.namespace.as_deref() {
10865        return namespace_prefixes(namespace)
10866            .into_iter()
10867            .any(|prefix| format!("{prefix}::{normalized}") == target_name);
10868    }
10869    target.package_name().is_empty() && normalized == target.identifier()
10870}
10871
10872fn parser_alias_target_names(alias: &CppAlias) -> Vec<String> {
10873    let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
10874    if normalized.contains("::") {
10875        return vec![normalized];
10876    }
10877    alias
10878        .namespace
10879        .as_deref()
10880        .map(namespace_prefixes)
10881        .map(|prefixes| {
10882            prefixes
10883                .into_iter()
10884                .map(|prefix| format!("{prefix}::{normalized}"))
10885                .collect()
10886        })
10887        .unwrap_or_else(|| vec![normalized])
10888}
10889
10890/// The declared return type text of a C++ function unit, with leading declaration specifiers
10891/// stripped, e.g. `T*` for `T* operator->()`.
10892pub fn cpp_function_return_type_text(
10893    analyzer: &CppGraphSource<'_>,
10894    function: &CodeUnit,
10895) -> Option<String> {
10896    let metadata = analyzer.signature_metadata(function);
10897    if !metadata.is_empty() {
10898        let first = metadata.first()?.return_type_text()?;
10899        return metadata
10900            .iter()
10901            .all(|metadata| metadata.return_type_text() == Some(first))
10902            .then(|| first.to_string());
10903    }
10904    let signature = cpp_function_signature_text(analyzer, function)?;
10905    cpp_function_return_type_text_from_signature(&signature)
10906}
10907
10908fn cpp_function_signature_text(
10909    analyzer: &CppGraphSource<'_>,
10910    function: &CodeUnit,
10911) -> Option<String> {
10912    function
10913        .signature()
10914        .filter(|signature| signature.contains(function.identifier()))
10915        .map(str::to_string)
10916        .or_else(|| analyzer.signatures(function).first().cloned())
10917        .or_else(|| analyzer.get_source(function, false))
10918}
10919
10920fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
10921    let open = signature.find('(')?;
10922    let name_at = cpp_function_name_start(signature, open)?;
10923    if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
10924        return Some(return_type);
10925    }
10926    let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
10927        .split_whitespace()
10928        .filter(|token| {
10929            !matches!(
10930                *token,
10931                "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
10932            )
10933        })
10934        .collect::<Vec<_>>()
10935        .join(" ");
10936    let type_text = type_text.trim();
10937    (!type_text.is_empty()).then(|| type_text.to_string())
10938}
10939
10940fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
10941    let before_parameters = &signature[..open];
10942    if let Some(operator_at) = before_parameters.rfind("operator") {
10943        let boundary = operator_at == 0
10944            || before_parameters[..operator_at]
10945                .chars()
10946                .next_back()
10947                .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
10948        if boundary {
10949            return Some(operator_at);
10950        }
10951    }
10952    before_parameters
10953        .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
10954        .map(|index| index + 1)
10955}
10956
10957fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
10958    let open = signature_from_name.find('(')?;
10959    let mut depth = 0i32;
10960    for (offset, ch) in signature_from_name[open..].char_indices() {
10961        match ch {
10962            '(' => depth += 1,
10963            ')' => {
10964                depth -= 1;
10965                if depth == 0 {
10966                    let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
10967                    let arrow = rest.find("->")?;
10968                    let return_type = rest[arrow + 2..].trim_start();
10969                    let return_type = return_type
10970                        .split(['{', ';'])
10971                        .next()
10972                        .unwrap_or(return_type)
10973                        .trim();
10974                    return (!return_type.is_empty()).then(|| return_type.to_string());
10975                }
10976            }
10977            _ => {}
10978        }
10979    }
10980    None
10981}
10982
10983/// Strip a leading `template <...>` parameter clause, leaving the declaration that follows.
10984/// Returns the input unchanged when there is no such clause.
10985fn cpp_strip_leading_template_clause(text: &str) -> &str {
10986    let trimmed = text.trim_start();
10987    let Some(rest) = trimmed.strip_prefix("template") else {
10988        return text;
10989    };
10990    let rest = rest.trim_start();
10991    if !rest.starts_with('<') {
10992        return text;
10993    }
10994    let mut depth = 0i32;
10995    for (offset, ch) in rest.char_indices() {
10996        match ch {
10997            '<' => depth += 1,
10998            '>' => {
10999                depth -= 1;
11000                if depth == 0 {
11001                    return rest[offset + ch.len_utf8()..].trim_start();
11002                }
11003            }
11004            _ => {}
11005        }
11006    }
11007    text
11008}
11009
11010pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
11011    // fqname-M4: `cpp_name_for` is a bespoke all-`::` rendering of the unit's
11012    // name (it replaces every `.`/`$` in `short_name` with `::`), which is NOT
11013    // the same string `default_parent_fq_name`/`fq().parent()` would render:
11014    // the structured `FqName`'s native cpp display deliberately keeps `.` (not
11015    // `::`) between a trailing `Package` segment and a following `Type`
11016    // segment (see `separator` in `fq_name.rs`, landed for issue #1163), so
11017    // popping the unit's own `fq()` segment would NOT reproduce this
11018    // fully-`::`-joined string. Left as a split on the locally-built
11019    // all-colon string rather than the unit's structured name.
11020    cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
11021        namespace
11022            .strip_prefix("anonymous_namespace::")
11023            .unwrap_or(namespace)
11024            .to_string()
11025    })
11026}
11027
11028fn namespace_prefixes(namespace: &str) -> Vec<String> {
11029    // `namespace` is built by `cpp_name_for`/`cpp_namespace_for` with every
11030    // non-`::` separator already converted to `::`, so re-tokenizing it with
11031    // the shared structured splitter and progressively popping the last
11032    // component reproduces the `rsplit_once("::")` outward walk exactly (same
11033    // shape as `cpp_qualifier_lookup_tiers`'s namespace-chain walk).
11034    let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11035        brokk_bifrost_core::analyzer::Language::Cpp,
11036        namespace,
11037    );
11038    let mut prefixes = Vec::new();
11039    while !parts.is_empty() {
11040        prefixes.push(parts.join("::"));
11041        parts.pop();
11042    }
11043    prefixes
11044}
11045
11046fn nearest_namespace_candidates(
11047    candidates: Vec<CodeUnit>,
11048    normalized: &str,
11049    lexical_namespace: Option<&str>,
11050) -> Vec<CodeUnit> {
11051    if normalized.contains("::") {
11052        return candidates;
11053    }
11054    if let Some(namespace) = lexical_namespace {
11055        for prefix in namespace_prefixes(namespace) {
11056            let scoped = candidates
11057                .iter()
11058                .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
11059                .cloned()
11060                .collect::<Vec<_>>();
11061            if !scoped.is_empty() {
11062                return scoped;
11063            }
11064        }
11065    }
11066    candidates
11067        .into_iter()
11068        .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
11069        .collect()
11070}
11071
11072pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
11073    let mut namespaces = Vec::new();
11074    let mut current = node.parent();
11075    while let Some(parent) = current {
11076        if parent.kind() == "namespace_definition"
11077            && let Some(name) = parent.child_by_field_name("name")
11078        {
11079            let namespace = normalize_cpp_reference_text(node_text(name, source));
11080            if !namespace.is_empty() {
11081                namespaces.push(namespace);
11082            }
11083        }
11084        current = parent.parent();
11085    }
11086    if namespaces.is_empty() {
11087        None
11088    } else {
11089        namespaces.reverse();
11090        Some(namespaces.join("::"))
11091    }
11092}
11093
11094/// Like [`precise_parent_of`], but drops module (namespace) parents. A namespace is a scope, not a
11095/// type or receiver, so namespace-scoped functions and constants resolve as free functions and
11096/// globals rather than members.
11097pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
11098    type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
11099}
11100
11101fn type_owner_resolution(
11102    analyzer: &CppGraphSource<'_>,
11103    code_unit: &CodeUnit,
11104) -> Option<ResolvedTypeOwner> {
11105    precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
11106}
11107
11108fn target_type_owner_resolution(
11109    analyzer: &CppGraphSource<'_>,
11110    code_unit: &CodeUnit,
11111) -> Option<ResolvedTypeOwner> {
11112    match type_owner_resolution(analyzer, code_unit) {
11113        Some(owner) if !owner.is_forward_declaration => Some(owner),
11114        Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
11115    }
11116}
11117
11118/// Recover method identity for an indexed out-of-line definition when the
11119/// analyzer has retained only its unique include-visible class forward
11120/// declaration. This is deliberately target-only: canonical declaration
11121/// resolution must continue to prefer the callable definition rather than
11122/// replacing it with the forward owner.
11123fn target_forward_owner_resolution(
11124    analyzer: &CppGraphSource<'_>,
11125    code_unit: &CodeUnit,
11126) -> Option<ResolvedTypeOwner> {
11127    if !code_unit.is_function() {
11128        return None;
11129    }
11130    let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
11131    let owner_fqn = code_unit.fq().parent()?.display(interner);
11132    let cpp = analyzer.cpp?;
11133    let mut visible_files = HashSet::default();
11134    collect_include_closure(
11135        analyzer,
11136        cpp.include_target_index(),
11137        code_unit.source(),
11138        &mut visible_files,
11139        None,
11140    );
11141    let mut forward = None;
11142    for candidate in analyzer
11143        .global_usage_definition_index()
11144        .fqn(&owner_fqn)
11145        .into_iter()
11146        .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
11147    {
11148        match cpp_class_declaration_strength(analyzer, candidate) {
11149            CppClassDeclarationStrength::Forward if forward.is_none() => {
11150                forward = Some(candidate.clone());
11151            }
11152            CppClassDeclarationStrength::Forward
11153            | CppClassDeclarationStrength::Full
11154            | CppClassDeclarationStrength::Unknown => return None,
11155        }
11156    }
11157    forward.map(|unit| ResolvedTypeOwner {
11158        unit,
11159        is_forward_declaration: true,
11160    })
11161}
11162
11163pub fn precise_parent_of(
11164    analyzer: &CppGraphSource<'_>,
11165    visibility: &VisibilityIndex<'_>,
11166    code_unit: &CodeUnit,
11167) -> Option<CodeUnit> {
11168    visibility.cached_precise_parent_of(analyzer, code_unit)
11169}
11170
11171fn precise_parent_resolution(
11172    analyzer: &CppGraphSource<'_>,
11173    code_unit: &CodeUnit,
11174) -> Option<ResolvedTypeOwner> {
11175    #[cfg(any(test, feature = "test-support"))]
11176    if let Some(cpp) = analyzer.cpp {
11177        cpp.record_cpp_parent_resolution_for_test();
11178    }
11179    if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
11180        return Some(ResolvedTypeOwner {
11181            unit,
11182            is_forward_declaration: false,
11183        });
11184    }
11185    let fallback = analyzer.parent_of(code_unit);
11186    // fqname-M4: `owner_name` is used both bare (passed standalone to the
11187    // owner-resolution calls below) and manually recombined with
11188    // `package_name()` a few lines down, so this needs the package-less
11189    // `short_name` owner specifically; `default_parent_fq_name`/`fq.parent()`
11190    // would render the package-qualified owner instead, changing both uses.
11191    let Some(owner_name) = code_unit
11192        .short_name()
11193        .rsplit_once('.')
11194        .map(|(owner, _)| owner)
11195    else {
11196        return fallback.map(|unit| ResolvedTypeOwner {
11197            unit,
11198            is_forward_declaration: false,
11199        });
11200    };
11201    let owner_fqn = if code_unit.package_name().is_empty() {
11202        owner_name.to_string()
11203    } else {
11204        format!("{}.{}", code_unit.package_name(), owner_name)
11205    };
11206    match same_source_owner(analyzer, code_unit, &owner_fqn, owner_name) {
11207        DirectOwnerResolution::UniqueFull(owner) => {
11208            return Some(ResolvedTypeOwner {
11209                unit: owner,
11210                is_forward_declaration: false,
11211            });
11212        }
11213        DirectOwnerResolution::Ambiguous => return None,
11214        DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
11215    }
11216    match directly_included_owner(analyzer, code_unit, &owner_fqn, owner_name) {
11217        DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
11218            unit: owner,
11219            is_forward_declaration: false,
11220        }),
11221        DirectOwnerResolution::Ambiguous => None,
11222        DirectOwnerResolution::ForwardsOnly(forwards) => {
11223            match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
11224                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
11225                    unit: owner,
11226                    is_forward_declaration: false,
11227                }),
11228                FullOwnerResolution::None => {
11229                    unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
11230                        unit,
11231                        is_forward_declaration: true,
11232                    })
11233                }
11234                FullOwnerResolution::Ambiguous => None,
11235            }
11236        }
11237        DirectOwnerResolution::None => {
11238            match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
11239                FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
11240                    unit: owner,
11241                    is_forward_declaration: false,
11242                }),
11243                FullOwnerResolution::Ambiguous => None,
11244                FullOwnerResolution::None => fallback
11245                    .filter(|parent| {
11246                        parent.source() == code_unit.source()
11247                            && parent.short_name() == owner_name
11248                            && parent.package_name() == code_unit.package_name()
11249                            && (!parent.is_class()
11250                                || cpp_class_declaration_strength(analyzer, parent)
11251                                    == CppClassDeclarationStrength::Full)
11252                    })
11253                    .map(|unit| ResolvedTypeOwner {
11254                        unit,
11255                        is_forward_declaration: false,
11256                    }),
11257            }
11258        }
11259    }
11260}
11261
11262fn exact_structural_type_parent(
11263    analyzer: &CppGraphSource<'_>,
11264    code_unit: &CodeUnit,
11265) -> Option<CodeUnit> {
11266    if !code_unit.is_function() && !code_unit.is_field() {
11267        return None;
11268    }
11269    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
11270    let cpp = analyzer.cpp?;
11271    let parent = cpp.structural_parent_of(code_unit)?;
11272    (!parent.is_module()
11273        && parent.source() == code_unit.source()
11274        && parent.package_name() == code_unit.package_name()
11275        && parent.short_name() == encoded_owner)
11276        .then_some(parent)
11277}
11278
11279fn same_source_owner(
11280    analyzer: &CppGraphSource<'_>,
11281    code_unit: &CodeUnit,
11282    owner_fqn: &str,
11283    owner_name: &str,
11284) -> DirectOwnerResolution {
11285    let candidates = analyzer
11286        .global_usage_definition_index()
11287        .fqn(owner_fqn)
11288        .into_iter()
11289        .filter(|candidate| {
11290            candidate.is_class()
11291                && candidate.source() == code_unit.source()
11292                && candidate.short_name() == owner_name
11293                && candidate.package_name() == code_unit.package_name()
11294        })
11295        .collect::<Vec<_>>();
11296    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
11297    classify_direct_owner_candidates(analyzer, candidates.into_iter())
11298}
11299
11300fn visible_full_cpp_owner(
11301    analyzer: &CppGraphSource<'_>,
11302    code_unit: &CodeUnit,
11303    owner_fqn: &str,
11304    owner_name: &str,
11305) -> FullOwnerResolution {
11306    let Some(cpp) = analyzer.cpp else {
11307        return FullOwnerResolution::None;
11308    };
11309    let mut visible_files = HashSet::default();
11310    collect_include_closure(
11311        analyzer,
11312        cpp.include_target_index(),
11313        code_unit.source(),
11314        &mut visible_files,
11315        None,
11316    );
11317    let candidates = analyzer
11318        .global_usage_definition_index()
11319        .fqn(owner_fqn)
11320        .into_iter()
11321        .filter(|candidate| {
11322            candidate.is_class()
11323                && candidate.short_name() == owner_name
11324                && candidate.package_name() == code_unit.package_name()
11325                && visible_files.contains(candidate.source())
11326        })
11327        .collect::<Vec<_>>();
11328    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
11329    let mut full_definition = None;
11330    for candidate in candidates {
11331        match cpp_class_declaration_strength(analyzer, candidate) {
11332            CppClassDeclarationStrength::Full if full_definition.is_some() => {
11333                return FullOwnerResolution::Ambiguous;
11334            }
11335            CppClassDeclarationStrength::Full => full_definition = Some(candidate.clone()),
11336            CppClassDeclarationStrength::Forward => {}
11337            CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
11338        }
11339    }
11340    full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
11341}
11342
11343pub enum DirectOwnerResolution {
11344    None,
11345    ForwardsOnly(Vec<CodeUnit>),
11346    UniqueFull(CodeUnit),
11347    Ambiguous,
11348}
11349
11350enum FullOwnerResolution {
11351    None,
11352    Unique(CodeUnit),
11353    Ambiguous,
11354}
11355
11356#[derive(Clone, Copy, PartialEq, Eq)]
11357pub enum CppClassDeclarationStrength {
11358    Full,
11359    Forward,
11360    Unknown,
11361}
11362
11363fn directly_included_owner(
11364    analyzer: &CppGraphSource<'_>,
11365    code_unit: &CodeUnit,
11366    owner_fqn: &str,
11367    owner_name: &str,
11368) -> DirectOwnerResolution {
11369    let Some(cpp) = analyzer.cpp else {
11370        return DirectOwnerResolution::None;
11371    };
11372    let imports = analyzer.import_statements(code_unit.source());
11373    let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
11374        .into_iter()
11375        .flat_map(|include| {
11376            resolve_include_targets_with_index(
11377                code_unit.source(),
11378                &include,
11379                cpp.include_target_index(),
11380            )
11381        })
11382        .collect();
11383    let candidates = analyzer
11384        .global_usage_definition_index()
11385        .fqn(owner_fqn)
11386        .into_iter()
11387        .filter(|candidate| {
11388            candidate.is_class()
11389                && candidate.short_name() == owner_name
11390                && candidate.package_name() == code_unit.package_name()
11391                && direct_includes.contains(candidate.source())
11392        })
11393        .collect::<Vec<_>>();
11394    let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
11395    classify_direct_owner_candidates(analyzer, candidates.into_iter())
11396}
11397
11398fn prefer_member_declaring_owners<'a>(
11399    analyzer: &CppGraphSource<'_>,
11400    member: &CodeUnit,
11401    candidates: Vec<&'a CodeUnit>,
11402) -> Vec<&'a CodeUnit> {
11403    let matching = candidates
11404        .iter()
11405        .copied()
11406        .filter(|owner| owner_declares_member(analyzer, owner, member))
11407        .collect::<Vec<_>>();
11408    if matching.is_empty() {
11409        candidates
11410    } else {
11411        matching
11412    }
11413}
11414
11415fn owner_declares_member(
11416    analyzer: &CppGraphSource<'_>,
11417    owner: &CodeUnit,
11418    member: &CodeUnit,
11419) -> bool {
11420    analyzer.direct_children(owner).into_iter().any(|child| {
11421        child.kind() == member.kind()
11422            && child.identifier() == member.identifier()
11423            && child.signature() == member.signature()
11424    })
11425}
11426
11427fn classify_direct_owner_candidates<'a>(
11428    analyzer: &CppGraphSource<'_>,
11429    candidates: impl Iterator<Item = &'a CodeUnit>,
11430) -> DirectOwnerResolution {
11431    collapse_owner_candidates(candidates.map(|candidate| {
11432        (
11433            candidate.clone(),
11434            cpp_class_declaration_strength(analyzer, candidate),
11435        )
11436    }))
11437}
11438
11439pub fn collapse_owner_candidates(
11440    candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
11441) -> DirectOwnerResolution {
11442    let mut full_definition = None;
11443    let mut forwards = Vec::new();
11444    for (candidate, strength) in candidates {
11445        match strength {
11446            CppClassDeclarationStrength::Full if full_definition.is_some() => {
11447                return DirectOwnerResolution::Ambiguous;
11448            }
11449            CppClassDeclarationStrength::Full => full_definition = Some(candidate),
11450            CppClassDeclarationStrength::Forward => forwards.push(candidate),
11451            CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
11452        }
11453    }
11454    if let Some(owner) = full_definition {
11455        DirectOwnerResolution::UniqueFull(owner)
11456    } else if !forwards.is_empty() {
11457        DirectOwnerResolution::ForwardsOnly(forwards)
11458    } else {
11459        DirectOwnerResolution::None
11460    }
11461}
11462
11463#[cfg(any(test, feature = "test-support"))]
11464pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
11465    unique_logical_forward_owner(forwards)
11466}
11467
11468fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
11469    let first = forwards.pop()?;
11470    forwards
11471        .iter()
11472        .all(|forward| same_logical_symbol(forward, &first))
11473        .then_some(first)
11474}
11475
11476pub fn cpp_class_declaration_strength(
11477    analyzer: &CppGraphSource<'_>,
11478    candidate: &CodeUnit,
11479) -> CppClassDeclarationStrength {
11480    if let Some(prepared) = analyzer
11481        .cpp
11482        .and_then(|cpp| cpp.prepared_syntax(candidate.source()))
11483    {
11484        return cpp_class_declaration_strength_in_tree(
11485            analyzer,
11486            candidate,
11487            prepared.source(),
11488            prepared.tree().root_node(),
11489        );
11490    }
11491    let Some(source) = analyzer.indexed_source(candidate.source()) else {
11492        return CppClassDeclarationStrength::Unknown;
11493    };
11494    #[cfg(any(test, feature = "test-support"))]
11495    if let Some(cpp) = analyzer.cpp {
11496        cpp.record_cpp_class_strength_parse_for_test();
11497    }
11498    let mut parser = Parser::new();
11499    if parser
11500        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11501        .is_err()
11502    {
11503        return CppClassDeclarationStrength::Unknown;
11504    }
11505    let Some(tree) = parser.parse(&source, None) else {
11506        return CppClassDeclarationStrength::Unknown;
11507    };
11508    cpp_class_declaration_strength_in_tree(analyzer, candidate, &source, tree.root_node())
11509}
11510
11511fn cpp_class_declaration_strength_in_tree(
11512    analyzer: &CppGraphSource<'_>,
11513    candidate: &CodeUnit,
11514    source: &str,
11515    root: Node<'_>,
11516) -> CppClassDeclarationStrength {
11517    let ranges = analyzer.ranges(candidate);
11518    let mut saw_forward = false;
11519    for range in ranges {
11520        let mut stack = vec![root];
11521        while let Some(node) = stack.pop() {
11522            if node.start_byte() == range.start_byte
11523                && recovered_fragmented_plain_class_has_body(
11524                    node,
11525                    source,
11526                    candidate.identifier(),
11527                    &range,
11528                )
11529            {
11530                return CppClassDeclarationStrength::Full;
11531            }
11532            if node.start_byte() > range.start_byte || node.end_byte() < range.start_byte {
11533                continue;
11534            }
11535            if node.start_byte() == range.start_byte && node.end_byte() == range.end_byte {
11536                if matches!(
11537                    node.kind(),
11538                    "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
11539                ) {
11540                    if cpp_class_node_has_body(node) {
11541                        return CppClassDeclarationStrength::Full;
11542                    }
11543                    saw_forward = true;
11544                } else if let Some(has_body) =
11545                    recovered_exported_class_has_body(node, source, candidate.identifier())
11546                {
11547                    if has_body {
11548                        return CppClassDeclarationStrength::Full;
11549                    }
11550                    saw_forward = true;
11551                }
11552            }
11553            let mut cursor = node.walk();
11554            stack.extend(node.named_children(&mut cursor));
11555        }
11556    }
11557    if saw_forward {
11558        CppClassDeclarationStrength::Forward
11559    } else {
11560        CppClassDeclarationStrength::Unknown
11561    }
11562}
11563
11564fn cpp_class_node_has_body(node: Node<'_>) -> bool {
11565    node.child_by_field_name("body").is_some() || {
11566        let mut cursor = node.walk();
11567        node.named_children(&mut cursor).any(|child| {
11568            matches!(
11569                child.kind(),
11570                "declaration_list" | "field_declaration_list" | "enumerator_list"
11571            )
11572        })
11573    }
11574}
11575
11576pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
11577    // fqname-M4: `owner_name` is used both bare and manually recombined with
11578    // `package_name()` below (same package-less short_name owner shape as
11579    // `precise_parent_resolution` above); `default_parent_fq_name` would
11580    // render the package-qualified owner instead, changing both uses.
11581    let owner_name = code_unit
11582        .short_name()
11583        .rsplit_once('.')
11584        .map(|(owner, _)| owner)?;
11585    let owner_fqn = if code_unit.package_name().is_empty() {
11586        owner_name.to_string()
11587    } else {
11588        format!("{}.{}", code_unit.package_name(), owner_name)
11589    };
11590    ctx.analyzer
11591        .global_usage_definition_index()
11592        .fqn(&owner_fqn)
11593        .into_iter()
11594        .find(|candidate| {
11595            candidate.is_class()
11596                && ctx.visibility.is_visible(ctx.file, candidate)
11597                && candidate.short_name() == owner_name
11598                && candidate.package_name() == code_unit.package_name()
11599        })
11600        .cloned()
11601}
11602
11603pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
11604    left.kind() == right.kind()
11605        && left.fq_name() == right.fq_name()
11606        && left.signature() == right.signature()
11607        && left.source() == right.source()
11608}
11609
11610pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
11611    same_symbol(left, right) || same_logical_symbol(left, right)
11612}
11613
11614pub fn same_visible_global_field_symbol(
11615    analyzer: &CppGraphSource<'_>,
11616    internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
11617    left: &CodeUnit,
11618    right: &CodeUnit,
11619) -> bool {
11620    if same_symbol(left, right) {
11621        return true;
11622    }
11623    if !same_logical_symbol(left, right) {
11624        return false;
11625    }
11626    if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
11627        || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
11628    {
11629        left.source() == right.source()
11630    } else {
11631        true
11632    }
11633}
11634
11635fn cpp_global_field_has_internal_linkage_cached(
11636    analyzer: &CppGraphSource<'_>,
11637    cache: &mut HashMap<CodeUnit, bool>,
11638    candidate: &CodeUnit,
11639) -> bool {
11640    if let Some(internal) = cache.get(candidate) {
11641        return *internal;
11642    }
11643    #[cfg(any(test, feature = "test-support"))]
11644    note_cpp_global_field_internal_linkage_classification_for_test();
11645    let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
11646    cache.insert(candidate.clone(), internal);
11647    internal
11648}
11649
11650#[cfg(any(test, feature = "test-support"))]
11651thread_local! {
11652    static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
11653}
11654
11655#[cfg(any(test, feature = "test-support"))]
11656fn note_cpp_global_field_internal_linkage_classification_for_test() {
11657    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
11658        count.set(count.get() + 1);
11659    });
11660}
11661
11662#[cfg(any(test, feature = "test-support"))]
11663pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
11664    body: impl FnOnce() -> T,
11665) -> (T, usize) {
11666    CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
11667        count.set(0);
11668        let result = body();
11669        let observed = count.get();
11670        count.set(0);
11671        (result, observed)
11672    })
11673}
11674
11675pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
11676    left.kind() == right.kind()
11677        && left.fq_name() == right.fq_name()
11678        && left.signature() == right.signature()
11679}
11680
11681pub fn cpp_global_field_has_internal_linkage(
11682    analyzer: &CppGraphSource<'_>,
11683    candidate: &CodeUnit,
11684) -> bool {
11685    if !candidate.is_field() || candidate.short_name().contains('.') {
11686        return false;
11687    }
11688    let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
11689        return false;
11690    };
11691    match local_linkage {
11692        CppFieldLinkage::Internal => true,
11693        CppFieldLinkage::External => false,
11694        CppFieldLinkage::InternalUnlessExternalPeer => {
11695            !cpp_global_field_linkage_peers(analyzer, candidate)
11696                .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, peer))
11697                .any(|linkage| matches!(linkage, CppFieldLinkage::External))
11698        }
11699    }
11700}
11701
11702fn cpp_global_field_linkage_peers<'a>(
11703    analyzer: &CppGraphSource<'a>,
11704    candidate: &'a CodeUnit,
11705) -> impl Iterator<Item = &'a CodeUnit> + 'a {
11706    // These peers are returned to the caller, so they must borrow the analyzer
11707    // for `'a` rather than a handle that dies with this call. `fqn` reads the
11708    // shards directly for exactly that reason.
11709    let fq_name = candidate.fq_name();
11710    analyzer
11711        .global_usage_definition_index()
11712        .fqn(&fq_name)
11713        .into_iter()
11714        .filter(move |peer| {
11715            if *peer == candidate {
11716                return false;
11717            }
11718            #[cfg(any(test, feature = "test-support"))]
11719            note_cpp_global_field_linkage_peer_inspection_for_test();
11720            same_logical_symbol(peer, candidate)
11721        })
11722}
11723
11724#[cfg(any(test, feature = "test-support"))]
11725thread_local! {
11726    static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
11727}
11728
11729#[cfg(any(test, feature = "test-support"))]
11730fn note_cpp_global_field_linkage_peer_inspection_for_test() {
11731    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
11732        count.set(count.get() + 1);
11733    });
11734}
11735
11736#[cfg(any(test, feature = "test-support"))]
11737pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
11738    body: impl FnOnce() -> T,
11739) -> (T, usize) {
11740    CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
11741        count.set(0);
11742        let result = body();
11743        let observed = count.get();
11744        count.set(0);
11745        (result, observed)
11746    })
11747}
11748
11749fn cpp_global_field_declaration_linkage(
11750    analyzer: &CppGraphSource<'_>,
11751    candidate: &CodeUnit,
11752) -> Option<CppFieldLinkage> {
11753    if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
11754        return Some(linkage);
11755    }
11756    let cpp = analyzer.cpp?;
11757    if let Some(prepared) = cpp.prepared_syntax(candidate.source()) {
11758        return cpp_global_field_declaration_linkage_in_tree(
11759            analyzer,
11760            candidate,
11761            prepared.source(),
11762            prepared.tree().root_node(),
11763        );
11764    }
11765    let source = analyzer.indexed_source(candidate.source())?;
11766    let mut parser = Parser::new();
11767    if parser
11768        .set_language(&tree_sitter_cpp::LANGUAGE.into())
11769        .is_err()
11770    {
11771        return None;
11772    }
11773    let tree = parser.parse(&source, None)?;
11774    cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
11775}
11776
11777fn cpp_global_field_declaration_linkage_in_tree(
11778    analyzer: &CppGraphSource<'_>,
11779    candidate: &CodeUnit,
11780    source: &str,
11781    root: Node<'_>,
11782) -> Option<CppFieldLinkage> {
11783    analyzer.ranges(candidate).iter().find_map(|range| {
11784        node_for_exact_range(root, range)
11785            .and_then(enclosing_cpp_field_declaration)
11786            .map(|declaration| cpp_field_declaration_linkage(declaration, source))
11787    })
11788}
11789
11790fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
11791    loop {
11792        if matches!(node.kind(), "declaration" | "field_declaration") {
11793            return Some(node);
11794        }
11795        node = node.parent()?;
11796    }
11797}
11798
11799#[cfg(test)]
11800mod tests {
11801    use super::*;
11802
11803    #[test]
11804    fn sort_lookup_units_totally_orders_every_identity_field() {
11805        let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
11806        let base = CodeUnit::with_signature(
11807            file.clone(),
11808            CodeUnitType::Function,
11809            "scope",
11810            "value",
11811            Some("()".to_string()),
11812            false,
11813        );
11814        let different_kind = CodeUnit::with_signature(
11815            file.clone(),
11816            CodeUnitType::Field,
11817            "scope",
11818            "value",
11819            Some("()".to_string()),
11820            false,
11821        );
11822        let synthetic = base.with_synthetic(true);
11823
11824        let interner = segment_interner();
11825        let mut member_fq = FqName::new();
11826        member_fq.push(interner.intern("scope", SegmentKind::Package));
11827        member_fq.push(interner.intern("value", SegmentKind::Member));
11828        let different_package_boundary = CodeUnit::from_fq(
11829            file.clone(),
11830            CodeUnitType::Function,
11831            member_fq,
11832            0,
11833            Some("()".to_string()),
11834            false,
11835        );
11836
11837        let mut unknown_fq = FqName::new();
11838        unknown_fq.push(interner.intern("scope", SegmentKind::Package));
11839        unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
11840        let different_segment_kind = CodeUnit::from_fq(
11841            file,
11842            CodeUnitType::Function,
11843            unknown_fq,
11844            1,
11845            Some("()".to_string()),
11846            false,
11847        );
11848
11849        let input = vec![
11850            base,
11851            different_kind,
11852            synthetic,
11853            different_package_boundary,
11854            different_segment_kind,
11855        ];
11856        let mut expected = input.clone();
11857        sort_lookup_units(&mut expected);
11858        assert!(expected.windows(2).all(|pair| {
11859            let mut ordered = pair.to_vec();
11860            sort_lookup_units(&mut ordered);
11861            ordered == pair && pair[0] != pair[1]
11862        }));
11863
11864        let mut reversed = input.clone();
11865        reversed.reverse();
11866        sort_lookup_units(&mut reversed);
11867        assert_eq!(reversed, expected);
11868
11869        let mut rotated = input;
11870        rotated.rotate_left(2);
11871        sort_lookup_units(&mut rotated);
11872        assert_eq!(rotated, expected);
11873    }
11874
11875    #[test]
11876    fn displaced_preprocessor_terminator_bounds_the_real_guard() {
11877        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";
11878        let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
11879        let parse = |source: &str| {
11880            let mut parser = Parser::new();
11881            parser
11882                .set_language(&tree_sitter_cpp::LANGUAGE.into())
11883                .expect("C++ grammar");
11884            parser.parse(source, None).expect("fixture tree")
11885        };
11886
11887        let tree = parse(damaged);
11888        let root = tree.root_node();
11889        let target = damaged.find("target").expect("target byte");
11890        let declaration = root
11891            .descendant_for_byte_range(target, target + "target".len())
11892            .and_then(|mut node| {
11893                loop {
11894                    if node.kind() == "declaration" {
11895                        break Some(node);
11896                    }
11897                    node = node.parent()?;
11898                }
11899            })
11900            .expect("declaration after the displaced terminator");
11901        let conditional = declaration
11902            .parent()
11903            .filter(|node| node.kind() == "preproc_ifdef")
11904            .expect("damaged inner conditional");
11905        let outer = conditional
11906            .parent()
11907            .filter(|node| node.kind() == "preproc_ifdef")
11908            .expect("ordinary outer include guard");
11909        let terminator = cpp_displaced_preprocessor_terminator(conditional)
11910            .expect("structured displaced #endif");
11911        assert_eq!(node_text(terminator, damaged), "#endif");
11912        assert!(terminator.end_byte() <= declaration.start_byte());
11913        assert!(!preprocessor_conditional_contains_descendant(
11914            conditional,
11915            declaration
11916        ));
11917        assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
11918        assert!(preprocessor_conditional_contains_descendant(
11919            outer,
11920            declaration
11921        ));
11922
11923        let tree = parse(guarded);
11924        let conditional = tree
11925            .root_node()
11926            .named_child(0)
11927            .filter(|node| node.kind() == "preproc_ifdef")
11928            .expect("ordinary conditional");
11929        let declaration = conditional
11930            .named_children(&mut conditional.walk())
11931            .find(|node| node.kind() == "declaration")
11932            .expect("guarded declaration");
11933        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
11934        assert!(preprocessor_conditional_contains_descendant(
11935            conditional,
11936            declaration
11937        ));
11938
11939        let damaged_alternative = format!(
11940            "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
11941            "UNUSED(value)\n".repeat(64)
11942        );
11943        let tree = parse(&damaged_alternative);
11944        let conditional = tree
11945            .root_node()
11946            .named_child(0)
11947            .filter(|node| node.kind() == "preproc_ifdef")
11948            .expect("outer conditional with an alternative");
11949        assert!(conditional.has_error());
11950        assert!(conditional.child_by_field_name("alternative").is_some());
11951        assert!(
11952            conditional
11953                .child(conditional.child_count() - 1)
11954                .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
11955        );
11956        assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
11957
11958        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";
11959        let tree = parse(split_declaration);
11960        let root = tree.root_node();
11961        let conditional = root
11962            .named_children(&mut root.walk())
11963            .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
11964            .expect("split declaration conditional");
11965        let target = split_declaration
11966            .find("static int target")
11967            .expect("target byte");
11968        let boundary =
11969            cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
11970        assert!(boundary.end_byte <= target, "{boundary:?}");
11971        assert_eq!(boundary.end_line, 9, "{boundary:?}");
11972        let target_node = root
11973            .descendant_for_byte_range(target, target + "static".len())
11974            .expect("target node");
11975        assert!(!preprocessor_conditional_contains_descendant(
11976            conditional,
11977            target_node
11978        ));
11979    }
11980
11981    #[test]
11982    fn fragmented_reference_guard_is_recovered() {
11983        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";
11984        let mut parser = Parser::new();
11985        parser
11986            .set_language(&tree_sitter_cpp::LANGUAGE.into())
11987            .expect("C++ grammar");
11988        let tree = parser.parse(source, None).expect("fixture tree");
11989        let start = source.rfind("helper").expect("reference byte");
11990        let node = tree
11991            .root_node()
11992            .descendant_for_byte_range(start, start + "helper".len())
11993            .expect("reference node");
11994        let mut expected = HashSet::default();
11995        expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
11996            vec![
11997                BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
11998                BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
11999            ],
12000        )));
12001        assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
12002    }
12003
12004    #[test]
12005    fn boolean_guard_normalization_proves_equivalence_and_implication() {
12006        let windows = BooleanGuardExpression::Defined("WIN32".to_string());
12007        let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
12008        let negated_windows_branch =
12009            BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
12010        let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
12011        assert_eq!(negated_windows_branch, portable);
12012
12013        let missing_a = BooleanGuardExpression::Undefined("A".to_string());
12014        let missing_b = BooleanGuardExpression::Undefined("B".to_string());
12015        let missing_c = BooleanGuardExpression::Undefined("C".to_string());
12016        let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
12017        let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
12018        assert!(fallback_branch.implies(&fallback_declaration));
12019        assert!(!fallback_declaration.implies(&fallback_branch));
12020    }
12021
12022    #[test]
12023    fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
12024        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";
12025        let mut parser = Parser::new();
12026        parser
12027            .set_language(&tree_sitter_cpp::LANGUAGE.into())
12028            .expect("C++ grammar");
12029        let tree = parser.parse(source, None).expect("fixture tree");
12030        let root = tree.root_node();
12031        let call = |marker: &str| {
12032            let start = source.find(marker).expect("call marker");
12033            let mut node = root
12034                .descendant_for_byte_range(start, start + "helper".len())
12035                .expect("call name node");
12036            loop {
12037                if node.kind() == "call_expression" {
12038                    break node;
12039                }
12040                node = node.parent().expect("call expression ancestor");
12041            }
12042        };
12043        let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
12044        let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
12045        let keyword_call = call("helper(NULL, template); /* bound */");
12046        let keyword_arguments = keyword_call
12047            .child_by_field_name("arguments")
12048            .expect("keyword argument list");
12049        assert_eq!(
12050            recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
12051            1
12052        );
12053        assert_eq!(
12054            recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
12055            0
12056        );
12057
12058        let unbound_call = call("helper(NULL, template); /* unbound */");
12059        let unbound_arguments = unbound_call
12060            .child_by_field_name("arguments")
12061            .expect("unbound argument list");
12062        assert_eq!(
12063            recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
12064            0
12065        );
12066    }
12067
12068    fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
12069        let mut parser = Parser::new();
12070        parser
12071            .set_language(&tree_sitter_cpp::LANGUAGE.into())
12072            .expect("C++ grammar");
12073        let tree = parser.parse(source, None).expect("C++ fixture tree");
12074        let mut stack = vec![tree.root_node()];
12075        while let Some(node) = stack.pop() {
12076            if node.kind() == "enum_specifier" {
12077                return flattened_macro_namespace_components(node, source);
12078            }
12079            let mut cursor = node.walk();
12080            let children = node.named_children(&mut cursor).collect::<Vec<_>>();
12081            stack.extend(children.into_iter().rev());
12082        }
12083        None
12084    }
12085
12086    #[test]
12087    fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
12088        let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
12089namespace detail
12090{
12091enum class value_t { null };
12092}
12093NLOHMANN_JSON_NAMESPACE_END
12094NLOHMANN_JSON_NAMESPACE_BEGIN
12095namespace next
12096{
12097struct next_type {};
12098}
12099NLOHMANN_JSON_NAMESPACE_END
12100"#;
12101        assert_eq!(
12102            first_enum_flattened_namespace(complete),
12103            Some(vec!["detail".to_string()])
12104        );
12105
12106        let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
12107        assert_eq!(
12108            first_enum_flattened_namespace(&stale_end),
12109            Some(vec!["detail".to_string()]),
12110            "a stale end marker before the begin marker must not replace the intended namespace"
12111        );
12112
12113        let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
12114namespace detail
12115{
12116enum class value_t { null };
12117}
12118struct next_type {};
12119"#;
12120        assert_eq!(first_enum_flattened_namespace(incomplete), None);
12121    }
12122}