Skip to main content

brokk_bifrost_cpp/graph/
resolver.rs

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