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